Realtime mission activity docs

James Peret 10 years ago
parent
commit
510e2cd8c4

+ 251 - 0
app/data/page-list.json

@@ -0,0 +1,251 @@
1
+{
2
+  "rulebook" :
3
+    [
4
+      {
5
+        "pageUrl" : "/rulebook/intro",
6
+        "slug" : "intro",
7
+        "title" : "Introduction",
8
+        "tagline" : "What is Avalanche Network?",
9
+        "content" : "data/rulebook/intro.md"
10
+      },
11
+      {
12
+        "pageUrl" : "/rulebook/missions",
13
+        "slug" : "missions",
14
+        "title" : "Mission Basics",
15
+        "tagline" : "Mechanics of how a mission works"
16
+      },
17
+      {
18
+        "pageUrl" : "/rulebook/tasks",
19
+        "slug" : "tasks",
20
+        "title" : "Tasks & Validations",
21
+        "tagline" : "How to automaticaly validate tasks"
22
+      }
23
+    ],
24
+  "rest-api-v1" :
25
+    [
26
+      {
27
+        "pageUrl" : "/rest-api-v1/get-missions",
28
+        "slug"  : "get-missions",
29
+        "title" : "Mission List",
30
+        "endpoint" : {
31
+          "type" : "GET",
32
+          "base" : "/missions"
33
+        },
34
+        "implemented" : true,
35
+        "description" : "You can get a list of missions and their details using this endpoint. Its also possible to do pagination and use filters to fine grain the returned results.",
36
+        "variables" : [
37
+          {
38
+            "name" : "count",
39
+            "description" : "The number of items in the list",
40
+            "variable_type" : "number"
41
+          },
42
+          {
43
+            "name" : "page",
44
+            "description" : "The page of items to be return. <i>Example: if count is 10 and page is 3, then the items 20-30 will be returned.</i>",
45
+            "variable_type" : "number"
46
+          },
47
+          {
48
+            "name" : "status",
49
+            "description" : "Filter for the missions status. Status can be <code>Planning</code>, <code>Launched</code>, <code>Completed</code>, <code>Failed</code> or <code>Canceled</code>",
50
+            "variable_type" : "string"
51
+          }
52
+        ],
53
+        "examples" : [
54
+          {
55
+            "name" : "cURL",
56
+            "language" : "bash",
57
+            "code" : "views/snipets/bash/get-missions-example.html"
58
+          },
59
+          {
60
+            "name" : "jQuery",
61
+            "language" : "javascript",
62
+            "code" : "views/snipets/jquery/get-missions-example.html"
63
+          }
64
+        ]
65
+      },
66
+      {
67
+        "pageUrl" : "/rest-api-v1/get-mission",
68
+        "slug"  : "get-mission",
69
+        "title" : "Mission Details",
70
+        "endpoint" : {
71
+          "type" : "GET",
72
+          "base" : "/missions/:slug"
73
+        },
74
+        "implemented" : true,
75
+        "description" : "Returns detailed information about a mission, including lists of all agents, all tasks and recent activity.",
76
+        "variables" : [
77
+          {
78
+            "name" : "slug",
79
+            "description" : "A unique shortname identifier for the mission.",
80
+            "variable_type" : "string"
81
+          }
82
+        ],
83
+        "examples" : [
84
+          {
85
+            "name" : "cURL",
86
+            "language" : "bash",
87
+            "code" : "views/snipets/bash/get-mission-example.html"
88
+          },
89
+          {
90
+            "name" : "jQuery",
91
+            "language" : "javascript",
92
+            "code" : "views/snipets/jquery/get-mission-example.html"
93
+          }
94
+        ]
95
+      },
96
+      {
97
+        "pageUrl" : "/rest-api-v1/post-mission",
98
+        "slug"  : "post-mission",
99
+        "title" : "Post Mission",
100
+        "endpoint" : {
101
+          "type" : "POST",
102
+          "base" : "/missions"
103
+        },
104
+        "description" : "Creates a new mission on the system and returns it back. A user token is needed for this request.",
105
+        "variables" : [
106
+          {
107
+            "name" : "slug",
108
+            "description" : "A shortname without spaces used as the mission identifier and url. This needs to be unique.",
109
+            "variable_type" : "string"
110
+          },
111
+          {
112
+            "name" : "title",
113
+            "description" : "The mission's title or name",
114
+            "variable_type" : "string"
115
+          }
116
+        ]
117
+      },
118
+      {
119
+        "pageUrl" : "/rest-api-v1/launch-mission",
120
+        "slug"  : "launch-mission",
121
+        "title" : "Launch Mission",
122
+        "endpoint" : {
123
+          "type" : "POST",
124
+          "base" : "/missions/:slug/launch"
125
+        }
126
+      },
127
+      {
128
+        "pageUrl" : "/rest-api-v1/delete-mission",
129
+        "slug"  : "delete-mission",
130
+        "title" : "Delete Mission",
131
+        "endpoint" : {
132
+          "type" : "DELETE",
133
+          "base" : "/missions/:slug"
134
+        }
135
+      },
136
+      {
137
+        "pageUrl" : "/rest-api-v1/get-mission-agents",
138
+        "slug"  : "get-mission-agents",
139
+        "title" : "Mission Agent List",
140
+        "endpoint" : {
141
+          "type" : "GET",
142
+          "base" : "/missions/:slug/agents"
143
+        }
144
+      },
145
+      {
146
+        "pageUrl" : "/rest-api-v1/get-mission-agent",
147
+        "slug"  : "get-mission-agent",
148
+        "title" : "Mission Agent Details",
149
+        "endpoint" : {
150
+          "type" : "GET",
151
+          "base" : "/missions/:slug/agents/:slug"
152
+        }
153
+      },
154
+      {
155
+        "pageUrl" : "/rest-api-v1/post-mission-agent",
156
+        "slug"  : "post-mission-agent",
157
+        "title" : "Post Mission Agent",
158
+        "endpoint" : {
159
+          "type" : "POST",
160
+          "base" : "/missions/:slug/agents"
161
+        }
162
+      },
163
+      {
164
+        "pageUrl" : "/rest-api-v1/delete-mission-agent",
165
+        "slug"  : "delete-mission-agent",
166
+        "title" : "Delete Mission Agent",
167
+        "endpoint" : {
168
+          "type" : "DELETE",
169
+          "base" : "/missions/:slug/agents/:slug"
170
+        }
171
+      },
172
+      {
173
+        "pageUrl" : "/rest-api-v1/get-mission-tasks",
174
+        "slug"  : "get-mission-tasks",
175
+        "title" : "Mission Task List",
176
+        "endpoint" : {
177
+          "type" : "GET",
178
+          "base" : "/missions/:slug/tasks"
179
+        }
180
+      },
181
+      {
182
+        "pageUrl" : "/rest-api-v1/get-mission-task",
183
+        "slug"  : "get-mission-task",
184
+        "title" : "Mission Task Details",
185
+        "endpoint" : {
186
+          "type" : "GET",
187
+          "base" : "/missions/:slug/tasks/:id"
188
+        }
189
+      },
190
+      {
191
+        "pageUrl" : "/rest-api-v1/mark-mission-task",
192
+        "slug"  : "mark-mission-task",
193
+        "title" : "Mark Mission Task",
194
+        "endpoint" : {
195
+          "type" : "POST",
196
+          "base" : "/missions/:slug/tasks/:id/mark"
197
+        }
198
+      },
199
+      {
200
+        "pageUrl" : "/rest-api-v1/post-mission-task",
201
+        "slug"  : "post-mission-task",
202
+        "title" : "Post Mission Task",
203
+        "endpoint" : {
204
+          "type" : "POST",
205
+          "base" : "/missions/:slug/tasks"
206
+        }
207
+      },
208
+      {
209
+        "pageUrl" : "/rest-api-v1/delete-mission-task",
210
+        "slug"  : "delete-mission-task",
211
+        "title" : "Delete Mission Task",
212
+        "endpoint" : {
213
+          "type" : "DELETE",
214
+          "base" : "/missions/:slug/tasks/:id"
215
+        }
216
+      }
217
+    ],
218
+    "realtime-api-v1" :
219
+    [
220
+      {
221
+        "pageUrl" : "/realtime-api-v1/realtime-mission-activity",
222
+        "slug"  : "realtime-mission-activity",
223
+        "title" : "Mission Activity",
224
+        "tagline" : "subscribe to a mission activity feed",
225
+        "description" : "Subscribe to a missions activity feed for realtime updates. Activities includes updates on all public messages, agents, tasks, rewards and mission status.",
226
+        "implemented" : true
227
+      },
228
+      {
229
+        "pageUrl" : "/realtime-api-v1/realtime-mission-agents",
230
+        "slug"  : "realtime-mission-agents",
231
+        "title" : "Mission Agents Status",
232
+        "tagline" : "Status off all agents on a mission",
233
+        "description" : "Subscribe to get changes from agents status on a specific mission. Basically when a mission agent changes connection status (online/offline) or location a message is broadcasted.",
234
+        "implemented" : false
235
+      },
236
+      {
237
+        "pageUrl" : "/realtime-api-v1/agent-status",
238
+        "slug"  : "agent-status",
239
+        "title" : "Agent Status",
240
+        "tagline" : "Agent online/offline status, position and stats",
241
+        "implemented" : false
242
+      },
243
+      {
244
+        "pageUrl" : "/realtime-api-v1/realtime-user-notifications",
245
+        "slug"  : "realtime-user-notifications",
246
+        "title" : "User Notifications",
247
+        "tagline" : "Personal user notification feed",
248
+        "implemented" : false
249
+      }
250
+    ]
251
+}

+ 0 - 191
app/data/rest-api-v1-pages.json

@@ -1,191 +0,0 @@
1
-[
2
-  {
3
-    "pageUrl" : "/rest-api-v1/get-missions",
4
-    "slug"  : "get-missions",
5
-    "title" : "Mission List",
6
-    "endpoint" : {
7
-      "type" : "GET",
8
-      "base" : "/missions"
9
-    },
10
-    "description" : "You can get a list of missions and their details using this endpoint. Its also possible to do pagination and use filters to fine grain the returned results.",
11
-    "variables" : [
12
-      {
13
-        "name" : "count",
14
-        "description" : "The number of items in the list",
15
-        "variable_type" : "number"
16
-      },
17
-      {
18
-        "name" : "page",
19
-        "description" : "The page of items to be return. <i>Example: if count is 10 and page is 3, then the items 20-30 will be returned.</i>",
20
-        "variable_type" : "number"
21
-      },
22
-      {
23
-        "name" : "status",
24
-        "description" : "Filter for the missions status. Status can be <code>Planning</code>, <code>Launched</code>, <code>Completed</code>, <code>Failed</code> or <code>Canceled</code>",
25
-        "variable_type" : "string"
26
-      }
27
-    ],
28
-    "examples" : [
29
-      {
30
-        "name" : "cURL",
31
-        "language" : "bash",
32
-        "code" : "views/snipets/bash/get-missions-example.html"
33
-      },
34
-      {
35
-        "name" : "jQuery",
36
-        "language" : "javascript",
37
-        "code" : "views/snipets/jquery/get-missions-example.html"
38
-      }
39
-    ]
40
-  },
41
-  {
42
-    "pageUrl" : "/rest-api-v1/get-mission",
43
-    "slug"  : "get-mission",
44
-    "title" : "Mission Details",
45
-    "endpoint" : {
46
-      "type" : "GET",
47
-      "base" : "/missions/:slug"
48
-    },
49
-    "description" : "Returns detailed information about a mission, including lists of all agents, all tasks and recent activity.",
50
-    "variables" : [
51
-      {
52
-        "name" : "slug",
53
-        "description" : "A unique shortname identifier for the mission.",
54
-        "variable_type" : "string"
55
-      }
56
-    ],
57
-    "examples" : [
58
-      {
59
-        "name" : "cURL",
60
-        "language" : "bash",
61
-        "code" : "views/snipets/bash/get-mission-example.html"
62
-      },
63
-      {
64
-        "name" : "jQuery",
65
-        "language" : "javascript",
66
-        "code" : "views/snipets/jquery/get-mission-example.html"
67
-      }
68
-    ]
69
-  },
70
-  {
71
-    "pageUrl" : "/rest-api-v1/post-mission",
72
-    "slug"  : "post-mission",
73
-    "title" : "Post Mission",
74
-    "endpoint" : {
75
-      "type" : "POST",
76
-      "base" : "/missions"
77
-    },
78
-    "description" : "Creates a new mission on the system and returns it back. A user token is needed for this request.",
79
-    "variables" : [
80
-      {
81
-        "name" : "slug",
82
-        "description" : "A shortname without spaces used as the mission identifier and url. This needs to be unique.",
83
-        "variable_type" : "string"
84
-      },
85
-      {
86
-        "name" : "title",
87
-        "description" : "The mission's title or name",
88
-        "variable_type" : "string"
89
-      }
90
-    ]
91
-  },
92
-  {
93
-    "pageUrl" : "/rest-api-v1/launch-mission",
94
-    "slug"  : "launch-mission",
95
-    "title" : "Launch Mission",
96
-    "endpoint" : {
97
-      "type" : "POST",
98
-      "base" : "/missions/:slug/launch"
99
-    }
100
-  },
101
-  {
102
-    "pageUrl" : "/rest-api-v1/delete-mission",
103
-    "slug"  : "delete-mission",
104
-    "title" : "Delete Mission",
105
-    "endpoint" : {
106
-      "type" : "DELETE",
107
-      "base" : "/missions/:slug"
108
-    }
109
-  },
110
-  {
111
-    "pageUrl" : "/rest-api-v1/get-mission-agents",
112
-    "slug"  : "get-mission-agents",
113
-    "title" : "Mission Agent List",
114
-    "endpoint" : {
115
-      "type" : "GET",
116
-      "base" : "/missions/:slug/agents"
117
-    }
118
-  },
119
-  {
120
-    "pageUrl" : "/rest-api-v1/get-mission-agent",
121
-    "slug"  : "get-mission-agent",
122
-    "title" : "Mission Agent Details",
123
-    "endpoint" : {
124
-      "type" : "GET",
125
-      "base" : "/missions/:slug/agents/:slug"
126
-    }
127
-  },
128
-  {
129
-    "pageUrl" : "/rest-api-v1/post-mission-agent",
130
-    "slug"  : "post-mission-agent",
131
-    "title" : "Post Mission Agent",
132
-    "endpoint" : {
133
-      "type" : "POST",
134
-      "base" : "/missions/:slug/agents"
135
-    }
136
-  },
137
-  {
138
-    "pageUrl" : "/rest-api-v1/delete-mission-agent",
139
-    "slug"  : "delete-mission-agent",
140
-    "title" : "Delete Mission Agent",
141
-    "endpoint" : {
142
-      "type" : "DELETE",
143
-      "base" : "/missions/:slug/agents/:slug"
144
-    }
145
-  },
146
-  {
147
-    "pageUrl" : "/rest-api-v1/get-mission-tasks",
148
-    "slug"  : "get-mission-tasks",
149
-    "title" : "Mission Task List",
150
-    "endpoint" : {
151
-      "type" : "GET",
152
-      "base" : "/missions/:slug/tasks"
153
-    }
154
-  },
155
-  {
156
-    "pageUrl" : "/rest-api-v1/get-mission-task",
157
-    "slug"  : "get-mission-task",
158
-    "title" : "Mission Task Details",
159
-    "endpoint" : {
160
-      "type" : "GET",
161
-      "base" : "/missions/:slug/tasks/:id"
162
-    }
163
-  },
164
-  {
165
-    "pageUrl" : "/rest-api-v1/mark-mission-task",
166
-    "slug"  : "mark-mission-task",
167
-    "title" : "Mark Mission Task",
168
-    "endpoint" : {
169
-      "type" : "POST",
170
-      "base" : "/missions/:slug/tasks/:id/mark"
171
-    }
172
-  },
173
-  {
174
-    "pageUrl" : "/rest-api-v1/post-mission-task",
175
-    "slug"  : "post-mission-task",
176
-    "title" : "Post Mission Task",
177
-    "endpoint" : {
178
-      "type" : "POST",
179
-      "base" : "/missions/:slug/tasks"
180
-    }
181
-  },
182
-  {
183
-    "pageUrl" : "/rest-api-v1/delete-mission-task",
184
-    "slug"  : "delete-mission-task",
185
-    "title" : "Delete Mission Task",
186
-    "endpoint" : {
187
-      "type" : "DELETE",
188
-      "base" : "/missions/:slug/tasks/:id"
189
-    }
190
-  }
191
-]

+ 3 - 0
app/data/rulebook/intro.md

@@ -0,0 +1,3 @@
1
+Welcome to the Avalanche Rulebook. The objective of this documentation is to explain, in depth, each part of the Avalanche Network mechanics.
2
+
3
+The main building block of Avalanche are [missions](rulebook/missions). Each mission has [agents](rulebook/agents), [tasks](rulebook/tasks), [rewards](rulebook/rewards) and [objectives](rulebook/objectives). Each of this parts will be explained with details in the following pages.

+ 9 - 1
app/index.html

@@ -10,6 +10,7 @@
10 10
     <!-- build:css(.) styles/vendor.css -->
11 11
     <!-- bower:css -->
12 12
     <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
13
+    <link rel="stylesheet" href="bower_components/ng-json-explorer/dist/angular-json-explorer.css" />
13 14
     <!-- endbower -->
14 15
     <!-- endbuild -->
15 16
     <!-- build:css(.tmp) styles/main.css -->
@@ -21,6 +22,7 @@
21 22
     <!-- endbuild -->
22 23
     <link rel="stylesheet" href="styles/tomorrow-night-eighties.css">
23 24
     <script src="scripts/libs/highlight.min.js"></script>
25
+    <script src="scripts/libs/faye-browser.js"></script>
24 26
 
25 27
   </head>
26 28
   <body ng-app="avalancheDocsApp">
@@ -44,12 +46,13 @@
44 46
        ga('send', 'pageview');
45 47
     </script>
46 48
     -->
47
-    
49
+
48 50
     <!-- build:js(.) scripts/vendor.js -->
49 51
     <!-- bower:js -->
50 52
     <script src="bower_components/jquery/dist/jquery.js"></script>
51 53
     <script src="bower_components/angular/angular.js"></script>
52 54
     <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
55
+    <script src="bower_components/angularjs/angular.js"></script>
53 56
     <script src="bower_components/angular-animate/angular-animate.js"></script>
54 57
     <script src="bower_components/angular-cookies/angular-cookies.js"></script>
55 58
     <script src="bower_components/angular-resource/angular-resource.js"></script>
@@ -62,6 +65,9 @@
62 65
     <script src="bower_components/spin.js/spin.js"></script>
63 66
     <script src="bower_components/angular-spinner/angular-spinner.js"></script>
64 67
     <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
68
+    <script src="bower_components/showdown/src/showdown.js"></script>
69
+    <script src="bower_components/angular-markdown-directive/markdown.js"></script>
70
+    <script src="bower_components/ng-json-explorer/dist/angular-json-explorer.js"></script>
65 71
     <!-- endbower -->
66 72
     <!-- endbuild -->
67 73
 
@@ -70,9 +76,11 @@
70 76
         <script src="scripts/controllers/getting-started-ctrl.js"></script>
71 77
         <script src="scripts/controllers/rulebook-ctrl.js"></script>
72 78
         <script src="scripts/controllers/rest-api-v1-ctrl.js"></script>
79
+        <script src="scripts/controllers/realtime-api-v1-ctrl.js"></script>
73 80
         <script src="scripts/controllers/about.js"></script>
74 81
         <script src="scripts/services/oauth-service.js"></script>
75 82
         <script src="scripts/services/data-service.js"></script>
83
+        <script src="scripts/services/realtime-service.js"></script>
76 84
         <script src="scripts/services/page-service.js"></script>
77 85
         <!-- endbuild -->
78 86
 

BIN
app/scripts/.DS_Store


+ 22 - 16
app/scripts/app.js

@@ -19,8 +19,9 @@ angular
19 19
     'ngTouch',
20 20
     'ui.bootstrap',
21 21
     'hljs',
22
-    'jsonFormatter',
23
-    'angularSpinner'
22
+    'ngJsonExplorer',
23
+    'angularSpinner',
24
+    'btford.markdown'
24 25
   ])
25 26
   .config(['$locationProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', function($locationProvider, $stateProvider, $urlRouterProvider, $httpProvider) {
26 27
     // Configs
@@ -41,19 +42,14 @@ angular
41 42
       controller: 'GettingStartedCtrl'
42 43
     })
43 44
     .state('rulebook', {
44
-      url: "/rulebook",
45
-      templateUrl: 'views/rulebook/index.html',
46
-      controller: 'RulebookCtrl'
47
-    })
48
-    .state('rulebook.intro', {
49
-      url: "/intro",
50
-      templateUrl: 'views/rulebook/introduction.html',
51
-      controller: 'AboutCtrl'
52
-    })
53
-    .state('rulebook.mission', {
54
-      url: "/mission",
55
-      templateUrl: 'views/rulebook/mission.html',
56
-      controller: 'AboutCtrl'
45
+      url: "/rulebook/:id",
46
+      templateUrl: 'views/rulebook.html',
47
+      controller: 'RulebookCtrl',
48
+      resolve: {
49
+        pageData: function($stateParams, PageService) {
50
+          return PageService.find("rulebook", $stateParams.id);
51
+        },
52
+      }
57 53
     })
58 54
     .state('rest-api-v1', {
59 55
       url: "/rest-api-v1/:id",
@@ -61,7 +57,17 @@ angular
61 57
       controller: 'RestAPIv1Ctrl',
62 58
       resolve: {
63 59
         pageData: function($stateParams, PageService) {
64
-          return PageService.find($stateParams.id);
60
+          return PageService.find("rest-api-v1", $stateParams.id);
61
+        },
62
+      }
63
+    })
64
+    .state('realtime-api-v1', {
65
+      url: "/realtime-api-v1/:id",
66
+      templateUrl: "views/realtime-api-v1.html",
67
+      controller: 'RealtimeAPIv1Ctrl',
68
+      resolve: {
69
+        pageData: function($stateParams, PageService) {
70
+          return PageService.find("realtime-api-v1", $stateParams.id);
65 71
         },
66 72
       }
67 73
     })

+ 4 - 0
app/scripts/controllers/getting-started-ctrl.js

@@ -19,6 +19,10 @@ angular.module('avalancheDocsApp')
19 19
       $location.path($sanitize(before_login_page)).replace();
20 20
     }
21 21
 
22
+    $scope.navActivePage = function (viewLocation) {
23
+        return viewLocation === "getting-started";
24
+    };
25
+
22 26
 
23 27
 
24 28
   }]);

+ 178 - 0
app/scripts/controllers/realtime-api-v1-ctrl.js

@@ -0,0 +1,178 @@
1
+'use strict';
2
+
3
+/**
4
+ * @ngdoc function
5
+ * @name avalancheDocsApp.controller:AboutCtrl
6
+ * @description
7
+ * # AboutCtrl
8
+ * Controller of the avalancheDocsApp
9
+ */
10
+angular.module('avalancheDocsApp')
11
+  .controller('RealtimeAPIv1Ctrl', ['$scope', '$location', '$cookies', '$window', '$rootScope', 'DataService', 'usSpinnerService', 'OAuthService', 'PageService', 'RealtimeService',  function ($scope, $location, $cookies, $window, $rootScope, DataService, usSpinnerService, OAuthService, PageService, RealtimeService) {
12
+
13
+    $scope.pageData = PageService.get();
14
+    $scope.pageList = PageService.all("realtime-api-v1");
15
+    //console.log("Loading page " + $scope.pageData.title)
16
+
17
+    $rootScope.$on('get-pages:finished', function() {
18
+      if(!$scope.$$phase) {
19
+        $scope.$apply(function(){
20
+          $scope.pageData = PageService.get();
21
+        });
22
+      } else {
23
+          $scope.pageData = PageService.get();
24
+      }
25
+    });
26
+
27
+    $scope.navActivePage = function (viewLocation) {
28
+        return viewLocation === "realtime-api-v1";
29
+    };
30
+
31
+    $scope.isActive = function (viewLocation) {
32
+        return viewLocation === $location.path();
33
+    };
34
+
35
+    $scope.token = $cookies.get('avalanche-docs-token');
36
+    if($scope.token == undefined) {
37
+      $scope.token = "none";
38
+      $scope.hideToken = true;
39
+    }
40
+
41
+    $scope.authorizeUser = function(){
42
+      console.log($location.url());
43
+      $cookies.put('avalanche_docs_before_login_page', $location.url());
44
+      $window.location.href = 'http://localhost:5000/oauth/authorize?client_id=d514f58c234d69ce1405f00dbef842bd785c09201b35a746d87306f5e69fd02b&redirect_uri=http://localhost:9000/&response_type=code';
45
+    }
46
+
47
+
48
+    // Initialize
49
+    $scope.response = {};
50
+    $scope.hideResult = true;
51
+    $scope.tokens = { availableOptions : [], selectedOption: []};
52
+    $scope.tokens.availableOptions[0] = 'none';
53
+    $scope.tokens.selectedToken = $scope.tokens.availableOptions[0];
54
+    $scope.data = {}
55
+    if(OAuthService.getToken() != "" && OAuthService.getToken() != undefined){
56
+      $scope.tokens.availableOptions[1] = OAuthService.getToken();
57
+      $scope.tokens.selectedToken = $scope.tokens.availableOptions[1];
58
+    }
59
+    DataService.getResponse()
60
+
61
+    // API Calls
62
+    $scope.getData = function(url){
63
+      usSpinnerService.spin('spinner-1');
64
+      DataService.get(url, $scope.processInputs(), $scope.tokens.selectedToken);
65
+    }
66
+    $scope.postData = function(url){
67
+      usSpinnerService.spin('spinner-1');
68
+      DataService.post(url, $scope.processInputs(), $scope.data, $scope.tokens.selectedToken);
69
+    }
70
+
71
+    $scope.processInputs = function(){
72
+      var inputs = [];
73
+      for (var i = 0; i < $scope.pageData.variables.length; i++) {
74
+        var field = $scope.pageData.variables[i].name;
75
+        inputs[field] = $scope.pageData.variables[i].value;
76
+      }
77
+      return inputs;
78
+    }
79
+
80
+
81
+    $scope.hideResponse = function() {
82
+      $scope.hideResult = true;
83
+    }
84
+    $rootScope.$on('get-data:finished', function() {
85
+      if(!$scope.$$phase) {
86
+        $scope.$apply(function(){
87
+          $scope.response = DataService.getResponse();
88
+          $scope.hideResult = false
89
+        });
90
+      } else {
91
+          $scope.response = DataService.getResponse();
92
+          $scope.hideResult = false
93
+      }
94
+      usSpinnerService.stop('spinner-1');
95
+    });
96
+
97
+    $rootScope.$on('auth:success', function() {
98
+      if(!$scope.$$phase) {
99
+        $scope.$apply(function(){
100
+          $scope.token[1] = OAuthService.getToken();
101
+          $scope.selectedToken = $scope.tokens.availableOptions[1];
102
+        });
103
+      } else {
104
+          $scope.token[1] = OAuthService.getToken();
105
+          $scope.selectedToken = $scope.tokens.availableOptions[1];
106
+      }
107
+    });
108
+
109
+    $scope.subscribing = false;
110
+    $scope.messages = [];
111
+    $scope.messages.add = function(message) {
112
+      this.push(message);
113
+    }
114
+
115
+    // Listen to data coming from the server via Faye
116
+    $scope.subscribe = function(url){
117
+      if($scope.has_disconected == true){
118
+        RealtimeService.connect();
119
+      }
120
+      RealtimeService.subscribe(url, function(msg) {
121
+        $scope.$apply(function() {
122
+          $scope.messages.add(msg);
123
+          console.log(msg);
124
+        });
125
+      });
126
+    }
127
+
128
+
129
+    // Post the data to the server and have it send to us
130
+    $scope.sendServer = function() {
131
+      $http.post('/', { foo: 'asd', message: $scope.message })
132
+        .success(function() {
133
+          $scope.message = '';
134
+        })
135
+        .error(function(data, status) {
136
+          $scope.messages.add('error', "Error doing POST to server: " + status);
137
+        });
138
+    };
139
+
140
+    // Send data to server via Faye
141
+    $scope.sendClient = function() {
142
+      Faye.publish('/fromclient', $scope.message);
143
+      $scope.messages.add('outgoing', $scope.message);
144
+      $scope.message = '';
145
+    };
146
+
147
+    // Misc
148
+    $rootScope.$on('realtime:offline', function() {
149
+      if(!$scope.$$phase) {
150
+        $scope.$apply(function(){
151
+          $scope.connection_status = "<div class=\"circle-error\"></div> Disconected";
152
+          $scope.has_connection = false;
153
+        })
154
+      } else {
155
+        $scope.connection_status = "<div class=\"circle-error\"></div> Disconected";
156
+        $scope.has_connection = false;
157
+      }
158
+    });
159
+    $rootScope.$on('realtime:online', function() {
160
+      if(!$scope.$$phase) {
161
+        $scope.$apply(function(){
162
+          $scope.connection_status = "<div class=\"circle-ok\"></div> Connected";
163
+          $scope.has_connection = true;
164
+          $scope.subscribing = true;
165
+        })
166
+      } else {
167
+        $scope.connection_status = "<div class=\"circle-ok\"></div> Connected";
168
+        $scope.has_connection = true;
169
+        $scope.subscribing = true;
170
+      }
171
+    });
172
+    $scope.disconect = function() {
173
+      RealtimeService.disconect();
174
+      $scope.has_disconected = true;
175
+    }
176
+
177
+
178
+  }]);

+ 15 - 2
app/scripts/controllers/rest-api-v1-ctrl.js

@@ -11,9 +11,22 @@ angular.module('avalancheDocsApp')
11 11
   .controller('RestAPIv1Ctrl', ['$scope', '$location', '$cookies', '$window', '$rootScope', 'DataService', 'usSpinnerService', 'OAuthService', 'PageService',  function ($scope, $location, $cookies, $window, $rootScope, DataService, usSpinnerService, OAuthService, PageService) {
12 12
 
13 13
     $scope.pageData = PageService.get();
14
-    $scope.pageList = PageService.all();
15
-    console.log("Loading page " + $scope.pageData.title)
14
+    $scope.pageList = PageService.all("rest-api-v1");
15
+    //console.log("Loading page " + $scope.pageData.title)
16 16
 
17
+    $rootScope.$on('get-pages:finished', function() {
18
+      if(!$scope.$$phase) {
19
+        $scope.$apply(function(){
20
+          $scope.pageData = PageService.get();
21
+        });
22
+      } else {
23
+          $scope.pageData = PageService.get();
24
+      }
25
+    });
26
+
27
+    $scope.navActivePage = function (viewLocation) {
28
+        return viewLocation === "rest-api-v1";
29
+    };
17 30
 
18 31
     $scope.isActive = function (viewLocation) {
19 32
         return viewLocation === $location.path();

+ 20 - 1
app/scripts/controllers/rulebook-ctrl.js

@@ -8,7 +8,26 @@
8 8
  * Controller of the avalancheDocsApp
9 9
  */
10 10
 angular.module('avalancheDocsApp')
11
-  .controller('RulebookCtrl', ['$scope', '$location', function ($scope, $location) {
11
+  .controller('RulebookCtrl', ['$scope', '$location', 'PageService', '$rootScope', function ($scope, $location, PageService, $rootScope) {
12
+
13
+    $scope.pageData = PageService.get();
14
+    $scope.pageList = PageService.all("rulebook");
15
+    //console.log("Loading page " + $scope.pageData.title)
16
+
17
+    $rootScope.$on('get-pages:finished', function() {
18
+      if(!$scope.$$phase) {
19
+        $scope.$apply(function(){
20
+          $scope.pageData = PageService.get();
21
+        });
22
+      } else {
23
+          $scope.pageData = PageService.get();
24
+      }
25
+    });
26
+
27
+    $scope.navActivePage = function (viewLocation) {
28
+        return viewLocation === "rulebook";
29
+    };
30
+
12 31
     $scope.isActive = function (viewLocation) {
13 32
         return viewLocation === $location.path();
14 33
     };

BIN
app/scripts/libs/.DS_Store


+ 2765 - 0
app/scripts/libs/faye-browser.js

@@ -0,0 +1,2765 @@
1
+(function() {
2
+'use strict';
3
+
4
+var Faye = {
5
+  VERSION:          '1.1.1',
6
+
7
+  BAYEUX_VERSION:   '1.0',
8
+  ID_LENGTH:        160,
9
+  JSONP_CALLBACK:   'jsonpcallback',
10
+  CONNECTION_TYPES: ['long-polling', 'cross-origin-long-polling', 'callback-polling', 'websocket', 'eventsource', 'in-process'],
11
+
12
+  MANDATORY_CONNECTION_TYPES: ['long-polling', 'callback-polling', 'in-process'],
13
+
14
+  ENV: (typeof window !== 'undefined') ? window : global,
15
+
16
+  extend: function(dest, source, overwrite) {
17
+    if (!source) return dest;
18
+    for (var key in source) {
19
+      if (!source.hasOwnProperty(key)) continue;
20
+      if (dest.hasOwnProperty(key) && overwrite === false) continue;
21
+      if (dest[key] !== source[key])
22
+        dest[key] = source[key];
23
+    }
24
+    return dest;
25
+  },
26
+
27
+  random: function(bitlength) {
28
+    bitlength = bitlength || this.ID_LENGTH;
29
+    var maxLength = Math.ceil(bitlength * Math.log(2) / Math.log(36));
30
+    var string = csprng(bitlength, 36);
31
+    while (string.length < maxLength) string = '0' + string;
32
+    return string;
33
+  },
34
+
35
+  validateOptions: function(options, validKeys) {
36
+    for (var key in options) {
37
+      if (this.indexOf(validKeys, key) < 0)
38
+        throw new Error('Unrecognized option: ' + key);
39
+    }
40
+  },
41
+
42
+  clientIdFromMessages: function(messages) {
43
+    var connect = this.filter([].concat(messages), function(message) {
44
+      return message.channel === '/meta/connect';
45
+    });
46
+    return connect[0] && connect[0].clientId;
47
+  },
48
+
49
+  copyObject: function(object) {
50
+    var clone, i, key;
51
+    if (object instanceof Array) {
52
+      clone = [];
53
+      i = object.length;
54
+      while (i--) clone[i] = Faye.copyObject(object[i]);
55
+      return clone;
56
+    } else if (typeof object === 'object') {
57
+      clone = (object === null) ? null : {};
58
+      for (key in object) clone[key] = Faye.copyObject(object[key]);
59
+      return clone;
60
+    } else {
61
+      return object;
62
+    }
63
+  },
64
+
65
+  commonElement: function(lista, listb) {
66
+    for (var i = 0, n = lista.length; i < n; i++) {
67
+      if (this.indexOf(listb, lista[i]) !== -1)
68
+        return lista[i];
69
+    }
70
+    return null;
71
+  },
72
+
73
+  indexOf: function(list, needle) {
74
+    if (list.indexOf) return list.indexOf(needle);
75
+
76
+    for (var i = 0, n = list.length; i < n; i++) {
77
+      if (list[i] === needle) return i;
78
+    }
79
+    return -1;
80
+  },
81
+
82
+  map: function(object, callback, context) {
83
+    if (object.map) return object.map(callback, context);
84
+    var result = [];
85
+
86
+    if (object instanceof Array) {
87
+      for (var i = 0, n = object.length; i < n; i++) {
88
+        result.push(callback.call(context || null, object[i], i));
89
+      }
90
+    } else {
91
+      for (var key in object) {
92
+        if (!object.hasOwnProperty(key)) continue;
93
+        result.push(callback.call(context || null, key, object[key]));
94
+      }
95
+    }
96
+    return result;
97
+  },
98
+
99
+  filter: function(array, callback, context) {
100
+    if (array.filter) return array.filter(callback, context);
101
+    var result = [];
102
+    for (var i = 0, n = array.length; i < n; i++) {
103
+      if (callback.call(context || null, array[i], i))
104
+        result.push(array[i]);
105
+    }
106
+    return result;
107
+  },
108
+
109
+  asyncEach: function(list, iterator, callback, context) {
110
+    var n       = list.length,
111
+        i       = -1,
112
+        calls   = 0,
113
+        looping = false;
114
+
115
+    var iterate = function() {
116
+      calls -= 1;
117
+      i += 1;
118
+      if (i === n) return callback && callback.call(context);
119
+      iterator(list[i], resume);
120
+    };
121
+
122
+    var loop = function() {
123
+      if (looping) return;
124
+      looping = true;
125
+      while (calls > 0) iterate();
126
+      looping = false;
127
+    };
128
+
129
+    var resume = function() {
130
+      calls += 1;
131
+      loop();
132
+    };
133
+    resume();
134
+  },
135
+
136
+  // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/
137
+  toJSON: function(object) {
138
+    if (!this.stringify) return JSON.stringify(object);
139
+
140
+    return this.stringify(object, function(key, value) {
141
+      return (this[key] instanceof Array) ? this[key] : value;
142
+    });
143
+  }
144
+};
145
+
146
+if (typeof module !== 'undefined')
147
+  module.exports = Faye;
148
+else if (typeof window !== 'undefined')
149
+  window.Faye = Faye;
150
+
151
+Faye.Class = function(parent, methods) {
152
+  if (typeof parent !== 'function') {
153
+    methods = parent;
154
+    parent  = Object;
155
+  }
156
+
157
+  var klass = function() {
158
+    if (!this.initialize) return this;
159
+    return this.initialize.apply(this, arguments) || this;
160
+  };
161
+
162
+  var bridge = function() {};
163
+  bridge.prototype = parent.prototype;
164
+
165
+  klass.prototype = new bridge();
166
+  Faye.extend(klass.prototype, methods);
167
+
168
+  return klass;
169
+};
170
+
171
+(function() {
172
+var EventEmitter = Faye.EventEmitter = function() {};
173
+
174
+/*
175
+Copyright Joyent, Inc. and other Node contributors. All rights reserved.
176
+Permission is hereby granted, free of charge, to any person obtaining a copy of
177
+this software and associated documentation files (the "Software"), to deal in
178
+the Software without restriction, including without limitation the rights to
179
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
180
+of the Software, and to permit persons to whom the Software is furnished to do
181
+so, subject to the following conditions:
182
+
183
+The above copyright notice and this permission notice shall be included in all
184
+copies or substantial portions of the Software.
185
+
186
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
187
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
188
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
189
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
190
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
191
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
192
+SOFTWARE.
193
+*/
194
+
195
+var isArray = typeof Array.isArray === 'function'
196
+    ? Array.isArray
197
+    : function (xs) {
198
+        return Object.prototype.toString.call(xs) === '[object Array]'
199
+    }
200
+;
201
+function indexOf (xs, x) {
202
+    if (xs.indexOf) return xs.indexOf(x);
203
+    for (var i = 0; i < xs.length; i++) {
204
+        if (x === xs[i]) return i;
205
+    }
206
+    return -1;
207
+}
208
+
209
+
210
+EventEmitter.prototype.emit = function(type) {
211
+  // If there is no 'error' event listener then throw.
212
+  if (type === 'error') {
213
+    if (!this._events || !this._events.error ||
214
+        (isArray(this._events.error) && !this._events.error.length))
215
+    {
216
+      if (arguments[1] instanceof Error) {
217
+        throw arguments[1]; // Unhandled 'error' event
218
+      } else {
219
+        throw new Error("Uncaught, unspecified 'error' event.");
220
+      }
221
+      return false;
222
+    }
223
+  }
224
+
225
+  if (!this._events) return false;
226
+  var handler = this._events[type];
227
+  if (!handler) return false;
228
+
229
+  if (typeof handler == 'function') {
230
+    switch (arguments.length) {
231
+      // fast cases
232
+      case 1:
233
+        handler.call(this);
234
+        break;
235
+      case 2:
236
+        handler.call(this, arguments[1]);
237
+        break;
238
+      case 3:
239
+        handler.call(this, arguments[1], arguments[2]);
240
+        break;
241
+      // slower
242
+      default:
243
+        var args = Array.prototype.slice.call(arguments, 1);
244
+        handler.apply(this, args);
245
+    }
246
+    return true;
247
+
248
+  } else if (isArray(handler)) {
249
+    var args = Array.prototype.slice.call(arguments, 1);
250
+
251
+    var listeners = handler.slice();
252
+    for (var i = 0, l = listeners.length; i < l; i++) {
253
+      listeners[i].apply(this, args);
254
+    }
255
+    return true;
256
+
257
+  } else {
258
+    return false;
259
+  }
260
+};
261
+
262
+// EventEmitter is defined in src/node_events.cc
263
+// EventEmitter.prototype.emit() is also defined there.
264
+EventEmitter.prototype.addListener = function(type, listener) {
265
+  if ('function' !== typeof listener) {
266
+    throw new Error('addListener only takes instances of Function');
267
+  }
268
+
269
+  if (!this._events) this._events = {};
270
+
271
+  // To avoid recursion in the case that type == "newListeners"! Before
272
+  // adding it to the listeners, first emit "newListeners".
273
+  this.emit('newListener', type, listener);
274
+
275
+  if (!this._events[type]) {
276
+    // Optimize the case of one listener. Don't need the extra array object.
277
+    this._events[type] = listener;
278
+  } else if (isArray(this._events[type])) {
279
+    // If we've already got an array, just append.
280
+    this._events[type].push(listener);
281
+  } else {
282
+    // Adding the second element, need to change to array.
283
+    this._events[type] = [this._events[type], listener];
284
+  }
285
+
286
+  return this;
287
+};
288
+
289
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
290
+
291
+EventEmitter.prototype.once = function(type, listener) {
292
+  var self = this;
293
+  self.on(type, function g() {
294
+    self.removeListener(type, g);
295
+    listener.apply(this, arguments);
296
+  });
297
+
298
+  return this;
299
+};
300
+
301
+EventEmitter.prototype.removeListener = function(type, listener) {
302
+  if ('function' !== typeof listener) {
303
+    throw new Error('removeListener only takes instances of Function');
304
+  }
305
+
306
+  // does not use listeners(), so no side effect of creating _events[type]
307
+  if (!this._events || !this._events[type]) return this;
308
+
309
+  var list = this._events[type];
310
+
311
+  if (isArray(list)) {
312
+    var i = indexOf(list, listener);
313
+    if (i < 0) return this;
314
+    list.splice(i, 1);
315
+    if (list.length == 0)
316
+      delete this._events[type];
317
+  } else if (this._events[type] === listener) {
318
+    delete this._events[type];
319
+  }
320
+
321
+  return this;
322
+};
323
+
324
+EventEmitter.prototype.removeAllListeners = function(type) {
325
+  if (arguments.length === 0) {
326
+    this._events = {};
327
+    return this;
328
+  }
329
+
330
+  // does not use listeners(), so no side effect of creating _events[type]
331
+  if (type && this._events && this._events[type]) this._events[type] = null;
332
+  return this;
333
+};
334
+
335
+EventEmitter.prototype.listeners = function(type) {
336
+  if (!this._events) this._events = {};
337
+  if (!this._events[type]) this._events[type] = [];
338
+  if (!isArray(this._events[type])) {
339
+    this._events[type] = [this._events[type]];
340
+  }
341
+  return this._events[type];
342
+};
343
+
344
+})();
345
+
346
+Faye.Namespace = Faye.Class({
347
+  initialize: function() {
348
+    this._used = {};
349
+  },
350
+
351
+  exists: function(id) {
352
+    return this._used.hasOwnProperty(id);
353
+  },
354
+
355
+  generate: function() {
356
+    var name = Faye.random();
357
+    while (this._used.hasOwnProperty(name))
358
+      name = Faye.random();
359
+    return this._used[name] = name;
360
+  },
361
+
362
+  release: function(id) {
363
+    delete this._used[id];
364
+  }
365
+});
366
+
367
+(function() {
368
+'use strict';
369
+
370
+var timeout = setTimeout, defer;
371
+
372
+if (typeof setImmediate === 'function')
373
+  defer = function(fn) { setImmediate(fn) };
374
+else if (typeof process === 'object' && process.nextTick)
375
+  defer = function(fn) { process.nextTick(fn) };
376
+else
377
+  defer = function(fn) { timeout(fn, 0) };
378
+
379
+var PENDING   = 0,
380
+    FULFILLED = 1,
381
+    REJECTED  = 2;
382
+
383
+var RETURN = function(x) { return x },
384
+    THROW  = function(x) { throw  x };
385
+
386
+var Promise = function(task) {
387
+  this._state       = PENDING;
388
+  this._onFulfilled = [];
389
+  this._onRejected  = [];
390
+
391
+  if (typeof task !== 'function') return;
392
+  var self = this;
393
+
394
+  task(function(value)  { fulfill(self, value) },
395
+       function(reason) { reject(self, reason) });
396
+};
397
+
398
+Promise.prototype.then = function(onFulfilled, onRejected) {
399
+  var next = new Promise();
400
+  registerOnFulfilled(this, onFulfilled, next);
401
+  registerOnRejected(this, onRejected, next);
402
+  return next;
403
+};
404
+
405
+var registerOnFulfilled = function(promise, onFulfilled, next) {
406
+  if (typeof onFulfilled !== 'function') onFulfilled = RETURN;
407
+  var handler = function(value) { invoke(onFulfilled, value, next) };
408
+
409
+  if (promise._state === PENDING) {
410
+    promise._onFulfilled.push(handler);
411
+  } else if (promise._state === FULFILLED) {
412
+    handler(promise._value);
413
+  }
414
+};
415
+
416
+var registerOnRejected = function(promise, onRejected, next) {
417
+  if (typeof onRejected !== 'function') onRejected = THROW;
418
+  var handler = function(reason) { invoke(onRejected, reason, next) };
419
+
420
+  if (promise._state === PENDING) {
421
+    promise._onRejected.push(handler);
422
+  } else if (promise._state === REJECTED) {
423
+    handler(promise._reason);
424
+  }
425
+};
426
+
427
+var invoke = function(fn, value, next) {
428
+  defer(function() { _invoke(fn, value, next) });
429
+};
430
+
431
+var _invoke = function(fn, value, next) {
432
+  var outcome;
433
+
434
+  try {
435
+    outcome = fn(value);
436
+  } catch (error) {
437
+    return reject(next, error);
438
+  }
439
+
440
+  if (outcome === next) {
441
+    reject(next, new TypeError('Recursive promise chain detected'));
442
+  } else {
443
+    fulfill(next, outcome);
444
+  }
445
+};
446
+
447
+var fulfill = Promise.fulfill = Promise.resolve = function(promise, value) {
448
+  var called = false, type, then;
449
+
450
+  try {
451
+    type = typeof value;
452
+    then = value !== null && (type === 'function' || type === 'object') && value.then;
453
+
454
+    if (typeof then !== 'function') return _fulfill(promise, value);
455
+
456
+    then.call(value, function(v) {
457
+      if (!(called ^ (called = true))) return;
458
+      fulfill(promise, v);
459
+    }, function(r) {
460
+      if (!(called ^ (called = true))) return;
461
+      reject(promise, r);
462
+    });
463
+  } catch (error) {
464
+    if (!(called ^ (called = true))) return;
465
+    reject(promise, error);
466
+  }
467
+};
468
+
469
+var _fulfill = function(promise, value) {
470
+  if (promise._state !== PENDING) return;
471
+
472
+  promise._state      = FULFILLED;
473
+  promise._value      = value;
474
+  promise._onRejected = [];
475
+
476
+  var onFulfilled = promise._onFulfilled, fn;
477
+  while (fn = onFulfilled.shift()) fn(value);
478
+};
479
+
480
+var reject = Promise.reject = function(promise, reason) {
481
+  if (promise._state !== PENDING) return;
482
+
483
+  promise._state       = REJECTED;
484
+  promise._reason      = reason;
485
+  promise._onFulfilled = [];
486
+
487
+  var onRejected = promise._onRejected, fn;
488
+  while (fn = onRejected.shift()) fn(reason);
489
+};
490
+
491
+Promise.all = function(promises) {
492
+  return new Promise(function(fulfill, reject) {
493
+    var list = [],
494
+         n   = promises.length,
495
+         i;
496
+
497
+    if (n === 0) return fulfill(list);
498
+
499
+    for (i = 0; i < n; i++) (function(promise, i) {
500
+      Promise.fulfilled(promise).then(function(value) {
501
+        list[i] = value;
502
+        if (--n === 0) fulfill(list);
503
+      }, reject);
504
+    })(promises[i], i);
505
+  });
506
+};
507
+
508
+Promise.defer = defer;
509
+
510
+Promise.deferred = Promise.pending = function() {
511
+  var tuple = {};
512
+
513
+  tuple.promise = new Promise(function(fulfill, reject) {
514
+    tuple.fulfill = tuple.resolve = fulfill;
515
+    tuple.reject  = reject;
516
+  });
517
+  return tuple;
518
+};
519
+
520
+Promise.fulfilled = Promise.resolved = function(value) {
521
+  return new Promise(function(fulfill, reject) { fulfill(value) });
522
+};
523
+
524
+Promise.rejected = function(reason) {
525
+  return new Promise(function(fulfill, reject) { reject(reason) });
526
+};
527
+
528
+if (typeof Faye === 'undefined')
529
+  module.exports = Promise;
530
+else
531
+  Faye.Promise = Promise;
532
+
533
+})();
534
+
535
+Faye.Set = Faye.Class({
536
+  initialize: function() {
537
+    this._index = {};
538
+  },
539
+
540
+  add: function(item) {
541
+    var key = (item.id !== undefined) ? item.id : item;
542
+    if (this._index.hasOwnProperty(key)) return false;
543
+    this._index[key] = item;
544
+    return true;
545
+  },
546
+
547
+  forEach: function(block, context) {
548
+    for (var key in this._index) {
549
+      if (this._index.hasOwnProperty(key))
550
+        block.call(context, this._index[key]);
551
+    }
552
+  },
553
+
554
+  isEmpty: function() {
555
+    for (var key in this._index) {
556
+      if (this._index.hasOwnProperty(key)) return false;
557
+    }
558
+    return true;
559
+  },
560
+
561
+  member: function(item) {
562
+    for (var key in this._index) {
563
+      if (this._index[key] === item) return true;
564
+    }
565
+    return false;
566
+  },
567
+
568
+  remove: function(item) {
569
+    var key = (item.id !== undefined) ? item.id : item;
570
+    var removed = this._index[key];
571
+    delete this._index[key];
572
+    return removed;
573
+  },
574
+
575
+  toArray: function() {
576
+    var array = [];
577
+    this.forEach(function(item) { array.push(item) });
578
+    return array;
579
+  }
580
+});
581
+
582
+Faye.URI = {
583
+  isURI: function(uri) {
584
+    return uri && uri.protocol && uri.host && uri.path;
585
+  },
586
+
587
+  isSameOrigin: function(uri) {
588
+    var location = Faye.ENV.location;
589
+    return uri.protocol === location.protocol &&
590
+           uri.hostname === location.hostname &&
591
+           uri.port     === location.port;
592
+  },
593
+
594
+  parse: function(url) {
595
+    if (typeof url !== 'string') return url;
596
+    var uri = {}, parts, query, pairs, i, n, data;
597
+
598
+    var consume = function(name, pattern) {
599
+      url = url.replace(pattern, function(match) {
600
+        uri[name] = match;
601
+        return '';
602
+      });
603
+      uri[name] = uri[name] || '';
604
+    };
605
+
606
+    consume('protocol', /^[a-z]+\:/i);
607
+    consume('host',     /^\/\/[^\/\?#]+/);
608
+
609
+    if (!/^\//.test(url) && !uri.host)
610
+      url = Faye.ENV.location.pathname.replace(/[^\/]*$/, '') + url;
611
+
612
+    consume('pathname', /^[^\?#]*/);
613
+    consume('search',   /^\?[^#]*/);
614
+    consume('hash',     /^#.*/);
615
+
616
+    uri.protocol = uri.protocol || Faye.ENV.location.protocol;
617
+
618
+    if (uri.host) {
619
+      uri.host     = uri.host.substr(2);
620
+      parts        = uri.host.split(':');
621
+      uri.hostname = parts[0];
622
+      uri.port     = parts[1] || '';
623
+    } else {
624
+      uri.host     = Faye.ENV.location.host;
625
+      uri.hostname = Faye.ENV.location.hostname;
626
+      uri.port     = Faye.ENV.location.port;
627
+    }
628
+
629
+    uri.pathname = uri.pathname || '/';
630
+    uri.path = uri.pathname + uri.search;
631
+
632
+    query = uri.search.replace(/^\?/, '');
633
+    pairs = query ? query.split('&') : [];
634
+    data  = {};
635
+
636
+    for (i = 0, n = pairs.length; i < n; i++) {
637
+      parts = pairs[i].split('=');
638
+      data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || '');
639
+    }
640
+
641
+    uri.query = data;
642
+
643
+    uri.href = this.stringify(uri);
644
+    return uri;
645
+  },
646
+
647
+  stringify: function(uri) {
648
+    var string = uri.protocol + '//' + uri.hostname;
649
+    if (uri.port) string += ':' + uri.port;
650
+    string += uri.pathname + this.queryString(uri.query) + (uri.hash || '');
651
+    return string;
652
+  },
653
+
654
+  queryString: function(query) {
655
+    var pairs = [];
656
+    for (var key in query) {
657
+      if (!query.hasOwnProperty(key)) continue;
658
+      pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(query[key]));
659
+    }
660
+    if (pairs.length === 0) return '';
661
+    return '?' + pairs.join('&');
662
+  }
663
+};
664
+
665
+Faye.Error = Faye.Class({
666
+  initialize: function(code, params, message) {
667
+    this.code    = code;
668
+    this.params  = Array.prototype.slice.call(params);
669
+    this.message = message;
670
+  },
671
+
672
+  toString: function() {
673
+    return this.code + ':' +
674
+           this.params.join(',') + ':' +
675
+           this.message;
676
+  }
677
+});
678
+
679
+Faye.Error.parse = function(message) {
680
+  message = message || '';
681
+  if (!Faye.Grammar.ERROR.test(message)) return new this(null, [], message);
682
+
683
+  var parts   = message.split(':'),
684
+      code    = parseInt(parts[0]),
685
+      params  = parts[1].split(','),
686
+      message = parts[2];
687
+
688
+  return new this(code, params, message);
689
+};
690
+
691
+
692
+
693
+
694
+Faye.Error.versionMismatch = function() {
695
+  return new this(300, arguments, 'Version mismatch').toString();
696
+};
697
+
698
+Faye.Error.conntypeMismatch = function() {
699
+  return new this(301, arguments, 'Connection types not supported').toString();
700
+};
701
+
702
+Faye.Error.extMismatch = function() {
703
+  return new this(302, arguments, 'Extension mismatch').toString();
704
+};
705
+
706
+Faye.Error.badRequest = function() {
707
+  return new this(400, arguments, 'Bad request').toString();
708
+};
709
+
710
+Faye.Error.clientUnknown = function() {
711
+  return new this(401, arguments, 'Unknown client').toString();
712
+};
713
+
714
+Faye.Error.parameterMissing = function() {
715
+  return new this(402, arguments, 'Missing required parameter').toString();
716
+};
717
+
718
+Faye.Error.channelForbidden = function() {
719
+  return new this(403, arguments, 'Forbidden channel').toString();
720
+};
721
+
722
+Faye.Error.channelUnknown = function() {
723
+  return new this(404, arguments, 'Unknown channel').toString();
724
+};
725
+
726
+Faye.Error.channelInvalid = function() {
727
+  return new this(405, arguments, 'Invalid channel').toString();
728
+};
729
+
730
+Faye.Error.extUnknown = function() {
731
+  return new this(406, arguments, 'Unknown extension').toString();
732
+};
733
+
734
+Faye.Error.publishFailed = function() {
735
+  return new this(407, arguments, 'Failed to publish').toString();
736
+};
737
+
738
+Faye.Error.serverError = function() {
739
+  return new this(500, arguments, 'Internal server error').toString();
740
+};
741
+
742
+
743
+Faye.Deferrable = {
744
+  then: function(callback, errback) {
745
+    var self = this;
746
+    if (!this._promise)
747
+      this._promise = new Faye.Promise(function(fulfill, reject) {
748
+        self._fulfill = fulfill;
749
+        self._reject  = reject;
750
+      });
751
+
752
+    if (arguments.length === 0)
753
+      return this._promise;
754
+    else
755
+      return this._promise.then(callback, errback);
756
+  },
757
+
758
+  callback: function(callback, context) {
759
+    return this.then(function(value) { callback.call(context, value) });
760
+  },
761
+
762
+  errback: function(callback, context) {
763
+    return this.then(null, function(reason) { callback.call(context, reason) });
764
+  },
765
+
766
+  timeout: function(seconds, message) {
767
+    this.then();
768
+    var self = this;
769
+    this._timer = Faye.ENV.setTimeout(function() {
770
+      self._reject(message);
771
+    }, seconds * 1000);
772
+  },
773
+
774
+  setDeferredStatus: function(status, value) {
775
+    if (this._timer) Faye.ENV.clearTimeout(this._timer);
776
+
777
+    this.then();
778
+
779
+    if (status === 'succeeded')
780
+      this._fulfill(value);
781
+    else if (status === 'failed')
782
+      this._reject(value);
783
+    else
784
+      delete this._promise;
785
+  }
786
+};
787
+
788
+Faye.Publisher = {
789
+  countListeners: function(eventType) {
790
+    return this.listeners(eventType).length;
791
+  },
792
+
793
+  bind: function(eventType, listener, context) {
794
+    var slice   = Array.prototype.slice,
795
+        handler = function() { listener.apply(context, slice.call(arguments)) };
796
+
797
+    this._listeners = this._listeners || [];
798
+    this._listeners.push([eventType, listener, context, handler]);
799
+    return this.on(eventType, handler);
800
+  },
801
+
802
+  unbind: function(eventType, listener, context) {
803
+    this._listeners = this._listeners || [];
804
+    var n = this._listeners.length, tuple;
805
+
806
+    while (n--) {
807
+      tuple = this._listeners[n];
808
+      if (tuple[0] !== eventType) continue;
809
+      if (listener && (tuple[1] !== listener || tuple[2] !== context)) continue;
810
+      this._listeners.splice(n, 1);
811
+      this.removeListener(eventType, tuple[3]);
812
+    }
813
+  }
814
+};
815
+
816
+Faye.extend(Faye.Publisher, Faye.EventEmitter.prototype);
817
+Faye.Publisher.trigger = Faye.Publisher.emit;
818
+
819
+Faye.Timeouts = {
820
+  addTimeout: function(name, delay, callback, context) {
821
+    this._timeouts = this._timeouts || {};
822
+    if (this._timeouts.hasOwnProperty(name)) return;
823
+    var self = this;
824
+    this._timeouts[name] = Faye.ENV.setTimeout(function() {
825
+      delete self._timeouts[name];
826
+      callback.call(context);
827
+    }, 1000 * delay);
828
+  },
829
+
830
+  removeTimeout: function(name) {
831
+    this._timeouts = this._timeouts || {};
832
+    var timeout = this._timeouts[name];
833
+    if (!timeout) return;
834
+    Faye.ENV.clearTimeout(timeout);
835
+    delete this._timeouts[name];
836
+  },
837
+
838
+  removeAllTimeouts: function() {
839
+    this._timeouts = this._timeouts || {};
840
+    for (var name in this._timeouts) this.removeTimeout(name);
841
+  }
842
+};
843
+
844
+Faye.Logging = {
845
+  LOG_LEVELS: {
846
+    fatal:  4,
847
+    error:  3,
848
+    warn:   2,
849
+    info:   1,
850
+    debug:  0
851
+  },
852
+
853
+  writeLog: function(messageArgs, level) {
854
+    if (!Faye.logger) return;
855
+
856
+    var args   = Array.prototype.slice.apply(messageArgs),
857
+        banner = '[Faye',
858
+        klass  = this.className,
859
+
860
+        message = args.shift().replace(/\?/g, function() {
861
+          try {
862
+            return Faye.toJSON(args.shift());
863
+          } catch (e) {
864
+            return '[Object]';
865
+          }
866
+        });
867
+
868
+    for (var key in Faye) {
869
+      if (klass) continue;
870
+      if (typeof Faye[key] !== 'function') continue;
871
+      if (this instanceof Faye[key]) klass = key;
872
+    }
873
+    if (klass) banner += '.' + klass;
874
+    banner += '] ';
875
+
876
+    if (typeof Faye.logger[level] === 'function')
877
+      Faye.logger[level](banner + message);
878
+    else if (typeof Faye.logger === 'function')
879
+      Faye.logger(banner + message);
880
+  }
881
+};
882
+
883
+(function() {
884
+  for (var key in Faye.Logging.LOG_LEVELS)
885
+    (function(level) {
886
+      Faye.Logging[level] = function() {
887
+        this.writeLog(arguments, level);
888
+      };
889
+    })(key);
890
+})();
891
+
892
+Faye.Grammar = {
893
+  CHANNEL_NAME:     /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,
894
+  CHANNEL_PATTERN:  /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,
895
+  ERROR:            /^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/,
896
+  VERSION:          /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/
897
+};
898
+
899
+Faye.Extensible = {
900
+  addExtension: function(extension) {
901
+    this._extensions = this._extensions || [];
902
+    this._extensions.push(extension);
903
+    if (extension.added) extension.added(this);
904
+  },
905
+
906
+  removeExtension: function(extension) {
907
+    if (!this._extensions) return;
908
+    var i = this._extensions.length;
909
+    while (i--) {
910
+      if (this._extensions[i] !== extension) continue;
911
+      this._extensions.splice(i,1);
912
+      if (extension.removed) extension.removed(this);
913
+    }
914
+  },
915
+
916
+  pipeThroughExtensions: function(stage, message, request, callback, context) {
917
+    this.debug('Passing through ? extensions: ?', stage, message);
918
+
919
+    if (!this._extensions) return callback.call(context, message);
920
+    var extensions = this._extensions.slice();
921
+
922
+    var pipe = function(message) {
923
+      if (!message) return callback.call(context, message);
924
+
925
+      var extension = extensions.shift();
926
+      if (!extension) return callback.call(context, message);
927
+
928
+      var fn = extension[stage];
929
+      if (!fn) return pipe(message);
930
+
931
+      if (fn.length >= 3) extension[stage](message, request, pipe);
932
+      else                extension[stage](message, pipe);
933
+    };
934
+    pipe(message);
935
+  }
936
+};
937
+
938
+Faye.extend(Faye.Extensible, Faye.Logging);
939
+
940
+Faye.Channel = Faye.Class({
941
+  initialize: function(name) {
942
+    this.id = this.name = name;
943
+  },
944
+
945
+  push: function(message) {
946
+    this.trigger('message', message);
947
+  },
948
+
949
+  isUnused: function() {
950
+    return this.countListeners('message') === 0;
951
+  }
952
+});
953
+
954
+Faye.extend(Faye.Channel.prototype, Faye.Publisher);
955
+
956
+Faye.extend(Faye.Channel, {
957
+  HANDSHAKE:    '/meta/handshake',
958
+  CONNECT:      '/meta/connect',
959
+  SUBSCRIBE:    '/meta/subscribe',
960
+  UNSUBSCRIBE:  '/meta/unsubscribe',
961
+  DISCONNECT:   '/meta/disconnect',
962
+
963
+  META:         'meta',
964
+  SERVICE:      'service',
965
+
966
+  expand: function(name) {
967
+    var segments = this.parse(name),
968
+        channels = ['/**', name];
969
+
970
+    var copy = segments.slice();
971
+    copy[copy.length - 1] = '*';
972
+    channels.push(this.unparse(copy));
973
+
974
+    for (var i = 1, n = segments.length; i < n; i++) {
975
+      copy = segments.slice(0, i);
976
+      copy.push('**');
977
+      channels.push(this.unparse(copy));
978
+    }
979
+
980
+    return channels;
981
+  },
982
+
983
+  isValid: function(name) {
984
+    return Faye.Grammar.CHANNEL_NAME.test(name) ||
985
+           Faye.Grammar.CHANNEL_PATTERN.test(name);
986
+  },
987
+
988
+  parse: function(name) {
989
+    if (!this.isValid(name)) return null;
990
+    return name.split('/').slice(1);
991
+  },
992
+
993
+  unparse: function(segments) {
994
+    return '/' + segments.join('/');
995
+  },
996
+
997
+  isMeta: function(name) {
998
+    var segments = this.parse(name);
999
+    return segments ? (segments[0] === this.META) : null;
1000
+  },
1001
+
1002
+  isService: function(name) {
1003
+    var segments = this.parse(name);
1004
+    return segments ? (segments[0] === this.SERVICE) : null;
1005
+  },
1006
+
1007
+  isSubscribable: function(name) {
1008
+    if (!this.isValid(name)) return null;
1009
+    return !this.isMeta(name) && !this.isService(name);
1010
+  },
1011
+
1012
+  Set: Faye.Class({
1013
+    initialize: function() {
1014
+      this._channels = {};
1015
+    },
1016
+
1017
+    getKeys: function() {
1018
+      var keys = [];
1019
+      for (var key in this._channels) keys.push(key);
1020
+      return keys;
1021
+    },
1022
+
1023
+    remove: function(name) {
1024
+      delete this._channels[name];
1025
+    },
1026
+
1027
+    hasSubscription: function(name) {
1028
+      return this._channels.hasOwnProperty(name);
1029
+    },
1030
+
1031
+    subscribe: function(names, callback, context) {
1032
+      var name;
1033
+      for (var i = 0, n = names.length; i < n; i++) {
1034
+        name = names[i];
1035
+        var channel = this._channels[name] = this._channels[name] || new Faye.Channel(name);
1036
+        if (callback) channel.bind('message', callback, context);
1037
+      }
1038
+    },
1039
+
1040
+    unsubscribe: function(name, callback, context) {
1041
+      var channel = this._channels[name];
1042
+      if (!channel) return false;
1043
+      channel.unbind('message', callback, context);
1044
+
1045
+      if (channel.isUnused()) {
1046
+        this.remove(name);
1047
+        return true;
1048
+      } else {
1049
+        return false;
1050
+      }
1051
+    },
1052
+
1053
+    distributeMessage: function(message) {
1054
+      var channels = Faye.Channel.expand(message.channel);
1055
+
1056
+      for (var i = 0, n = channels.length; i < n; i++) {
1057
+        var channel = this._channels[channels[i]];
1058
+        if (channel) channel.trigger('message', message.data);
1059
+      }
1060
+    }
1061
+  })
1062
+});
1063
+
1064
+Faye.Publication = Faye.Class(Faye.Deferrable);
1065
+
1066
+Faye.Subscription = Faye.Class({
1067
+  initialize: function(client, channels, callback, context) {
1068
+    this._client    = client;
1069
+    this._channels  = channels;
1070
+    this._callback  = callback;
1071
+    this._context     = context;
1072
+    this._cancelled = false;
1073
+  },
1074
+
1075
+  cancel: function() {
1076
+    if (this._cancelled) return;
1077
+    this._client.unsubscribe(this._channels, this._callback, this._context);
1078
+    this._cancelled = true;
1079
+  },
1080
+
1081
+  unsubscribe: function() {
1082
+    this.cancel();
1083
+  }
1084
+});
1085
+
1086
+Faye.extend(Faye.Subscription.prototype, Faye.Deferrable);
1087
+
1088
+Faye.Client = Faye.Class({
1089
+  UNCONNECTED:        1,
1090
+  CONNECTING:         2,
1091
+  CONNECTED:          3,
1092
+  DISCONNECTED:       4,
1093
+
1094
+  HANDSHAKE:          'handshake',
1095
+  RETRY:              'retry',
1096
+  NONE:               'none',
1097
+
1098
+  CONNECTION_TIMEOUT: 60,
1099
+
1100
+  DEFAULT_ENDPOINT:   '/bayeux',
1101
+  INTERVAL:           0,
1102
+
1103
+  initialize: function(endpoint, options) {
1104
+    this.info('New client created for ?', endpoint);
1105
+    options = options || {};
1106
+
1107
+    Faye.validateOptions(options, ['interval', 'timeout', 'endpoints', 'proxy', 'retry', 'scheduler', 'websocketExtensions', 'tls', 'ca']);
1108
+
1109
+    this._endpoint   = endpoint || this.DEFAULT_ENDPOINT;
1110
+    this._channels   = new Faye.Channel.Set();
1111
+    this._dispatcher = new Faye.Dispatcher(this, this._endpoint, options);
1112
+
1113
+    this._messageId = 0;
1114
+    this._state     = this.UNCONNECTED;
1115
+
1116
+    this._responseCallbacks = {};
1117
+
1118
+    this._advice = {
1119
+      reconnect: this.RETRY,
1120
+      interval:  1000 * (options.interval || this.INTERVAL),
1121
+      timeout:   1000 * (options.timeout  || this.CONNECTION_TIMEOUT)
1122
+    };
1123
+    this._dispatcher.timeout = this._advice.timeout / 1000;
1124
+
1125
+    this._dispatcher.bind('message', this._receiveMessage, this);
1126
+
1127
+    if (Faye.Event && Faye.ENV.onbeforeunload !== undefined)
1128
+      Faye.Event.on(Faye.ENV, 'beforeunload', function() {
1129
+        if (Faye.indexOf(this._dispatcher._disabled, 'autodisconnect') < 0)
1130
+          this.disconnect();
1131
+      }, this);
1132
+  },
1133
+
1134
+  addWebsocketExtension: function(extension) {
1135
+    return this._dispatcher.addWebsocketExtension(extension);
1136
+  },
1137
+
1138
+  disable: function(feature) {
1139
+    return this._dispatcher.disable(feature);
1140
+  },
1141
+
1142
+  setHeader: function(name, value) {
1143
+    return this._dispatcher.setHeader(name, value);
1144
+  },
1145
+
1146
+  // Request
1147
+  // MUST include:  * channel
1148
+  //                * version
1149
+  //                * supportedConnectionTypes
1150
+  // MAY include:   * minimumVersion
1151
+  //                * ext
1152
+  //                * id
1153
+  //
1154
+  // Success Response                             Failed Response
1155
+  // MUST include:  * channel                     MUST include:  * channel
1156
+  //                * version                                    * successful
1157
+  //                * supportedConnectionTypes                   * error
1158
+  //                * clientId                    MAY include:   * supportedConnectionTypes
1159
+  //                * successful                                 * advice
1160
+  // MAY include:   * minimumVersion                             * version
1161
+  //                * advice                                     * minimumVersion
1162
+  //                * ext                                        * ext
1163
+  //                * id                                         * id
1164
+  //                * authSuccessful
1165
+  handshake: function(callback, context) {
1166
+    if (this._advice.reconnect === this.NONE) return;
1167
+    if (this._state !== this.UNCONNECTED) return;
1168
+
1169
+    this._state = this.CONNECTING;
1170
+    var self = this;
1171
+
1172
+    this.info('Initiating handshake with ?', Faye.URI.stringify(this._endpoint));
1173
+    this._dispatcher.selectTransport(Faye.MANDATORY_CONNECTION_TYPES);
1174
+
1175
+    this._sendMessage({
1176
+      channel:                  Faye.Channel.HANDSHAKE,
1177
+      version:                  Faye.BAYEUX_VERSION,
1178
+      supportedConnectionTypes: this._dispatcher.getConnectionTypes()
1179
+
1180
+    }, {}, function(response) {
1181
+
1182
+      if (response.successful) {
1183
+        this._state = this.CONNECTED;
1184
+        this._dispatcher.clientId  = response.clientId;
1185
+
1186
+        this._dispatcher.selectTransport(response.supportedConnectionTypes);
1187
+
1188
+        this.info('Handshake successful: ?', this._dispatcher.clientId);
1189
+
1190
+        this.subscribe(this._channels.getKeys(), true);
1191
+        if (callback) Faye.Promise.defer(function() { callback.call(context) });
1192
+
1193
+      } else {
1194
+        this.info('Handshake unsuccessful');
1195
+        Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._dispatcher.retry * 1000);
1196
+        this._state = this.UNCONNECTED;
1197
+      }
1198
+    }, this);
1199
+  },
1200
+
1201
+  // Request                              Response
1202
+  // MUST include:  * channel             MUST include:  * channel
1203
+  //                * clientId                           * successful
1204
+  //                * connectionType                     * clientId
1205
+  // MAY include:   * ext                 MAY include:   * error
1206
+  //                * id                                 * advice
1207
+  //                                                     * ext
1208
+  //                                                     * id
1209
+  //                                                     * timestamp
1210
+  connect: function(callback, context) {
1211
+    if (this._advice.reconnect === this.NONE) return;
1212
+    if (this._state === this.DISCONNECTED) return;
1213
+
1214
+    if (this._state === this.UNCONNECTED)
1215
+      return this.handshake(function() { this.connect(callback, context) }, this);
1216
+
1217
+    this.callback(callback, context);
1218
+    if (this._state !== this.CONNECTED) return;
1219
+
1220
+    this.info('Calling deferred actions for ?', this._dispatcher.clientId);
1221
+    this.setDeferredStatus('succeeded');
1222
+    this.setDeferredStatus('unknown');
1223
+
1224
+    if (this._connectRequest) return;
1225
+    this._connectRequest = true;
1226
+
1227
+    this.info('Initiating connection for ?', this._dispatcher.clientId);
1228
+
1229
+    this._sendMessage({
1230
+      channel:        Faye.Channel.CONNECT,
1231
+      clientId:       this._dispatcher.clientId,
1232
+      connectionType: this._dispatcher.connectionType
1233
+
1234
+    }, {}, this._cycleConnection, this);
1235
+  },
1236
+
1237
+  // Request                              Response
1238
+  // MUST include:  * channel             MUST include:  * channel
1239
+  //                * clientId                           * successful
1240
+  // MAY include:   * ext                                * clientId
1241
+  //                * id                  MAY include:   * error
1242
+  //                                                     * ext
1243
+  //                                                     * id
1244
+  disconnect: function() {
1245
+    if (this._state !== this.CONNECTED) return;
1246
+    this._state = this.DISCONNECTED;
1247
+
1248
+    this.info('Disconnecting ?', this._dispatcher.clientId);
1249
+    var promise = new Faye.Publication();
1250
+
1251
+    this._sendMessage({
1252
+      channel:  Faye.Channel.DISCONNECT,
1253
+      clientId: this._dispatcher.clientId
1254
+
1255
+    }, {}, function(response) {
1256
+      if (response.successful) {
1257
+        this._dispatcher.close();
1258
+        promise.setDeferredStatus('succeeded');
1259
+      } else {
1260
+        promise.setDeferredStatus('failed', Faye.Error.parse(response.error));
1261
+      }
1262
+    }, this);
1263
+
1264
+    this.info('Clearing channel listeners for ?', this._dispatcher.clientId);
1265
+    this._channels = new Faye.Channel.Set();
1266
+
1267
+    return promise;
1268
+  },
1269
+
1270
+  // Request                              Response
1271
+  // MUST include:  * channel             MUST include:  * channel
1272
+  //                * clientId                           * successful
1273
+  //                * subscription                       * clientId
1274
+  // MAY include:   * ext                                * subscription
1275
+  //                * id                  MAY include:   * error
1276
+  //                                                     * advice
1277
+  //                                                     * ext
1278
+  //                                                     * id
1279
+  //                                                     * timestamp
1280
+  subscribe: function(channel, callback, context) {
1281
+    if (channel instanceof Array)
1282
+      return Faye.map(channel, function(c) {
1283
+        return this.subscribe(c, callback, context);
1284
+      }, this);
1285
+
1286
+    var subscription = new Faye.Subscription(this, channel, callback, context),
1287
+        force        = (callback === true),
1288
+        hasSubscribe = this._channels.hasSubscription(channel);
1289
+
1290
+    if (hasSubscribe && !force) {
1291
+      this._channels.subscribe([channel], callback, context);
1292
+      subscription.setDeferredStatus('succeeded');
1293
+      return subscription;
1294
+    }
1295
+
1296
+    this.connect(function() {
1297
+      this.info('Client ? attempting to subscribe to ?', this._dispatcher.clientId, channel);
1298
+      if (!force) this._channels.subscribe([channel], callback, context);
1299
+
1300
+      this._sendMessage({
1301
+        channel:      Faye.Channel.SUBSCRIBE,
1302
+        clientId:     this._dispatcher.clientId,
1303
+        subscription: channel
1304
+
1305
+      }, {}, function(response) {
1306
+        if (!response.successful) {
1307
+          subscription.setDeferredStatus('failed', Faye.Error.parse(response.error));
1308
+          return this._channels.unsubscribe(channel, callback, context);
1309
+        }
1310
+
1311
+        var channels = [].concat(response.subscription);
1312
+        this.info('Subscription acknowledged for ? to ?', this._dispatcher.clientId, channels);
1313
+        subscription.setDeferredStatus('succeeded');
1314
+      }, this);
1315
+    }, this);
1316
+
1317
+    return subscription;
1318
+  },
1319
+
1320
+  // Request                              Response
1321
+  // MUST include:  * channel             MUST include:  * channel
1322
+  //                * clientId                           * successful
1323
+  //                * subscription                       * clientId
1324
+  // MAY include:   * ext                                * subscription
1325
+  //                * id                  MAY include:   * error
1326
+  //                                                     * advice
1327
+  //                                                     * ext
1328
+  //                                                     * id
1329
+  //                                                     * timestamp
1330
+  unsubscribe: function(channel, callback, context) {
1331
+    if (channel instanceof Array)
1332
+      return Faye.map(channel, function(c) {
1333
+        return this.unsubscribe(c, callback, context);
1334
+      }, this);
1335
+
1336
+    var dead = this._channels.unsubscribe(channel, callback, context);
1337
+    if (!dead) return;
1338
+
1339
+    this.connect(function() {
1340
+      this.info('Client ? attempting to unsubscribe from ?', this._dispatcher.clientId, channel);
1341
+
1342
+      this._sendMessage({
1343
+        channel:      Faye.Channel.UNSUBSCRIBE,
1344
+        clientId:     this._dispatcher.clientId,
1345
+        subscription: channel
1346
+
1347
+      }, {}, function(response) {
1348
+        if (!response.successful) return;
1349
+
1350
+        var channels = [].concat(response.subscription);
1351
+        this.info('Unsubscription acknowledged for ? from ?', this._dispatcher.clientId, channels);
1352
+      }, this);
1353
+    }, this);
1354
+  },
1355
+
1356
+  // Request                              Response
1357
+  // MUST include:  * channel             MUST include:  * channel
1358
+  //                * data                               * successful
1359
+  // MAY include:   * clientId            MAY include:   * id
1360
+  //                * id                                 * error
1361
+  //                * ext                                * ext
1362
+  publish: function(channel, data, options) {
1363
+    Faye.validateOptions(options || {}, ['attempts', 'deadline']);
1364
+    var publication = new Faye.Publication();
1365
+
1366
+    this.connect(function() {
1367
+      this.info('Client ? queueing published message to ?: ?', this._dispatcher.clientId, channel, data);
1368
+
1369
+      this._sendMessage({
1370
+        channel:  channel,
1371
+        data:     data,
1372
+        clientId: this._dispatcher.clientId
1373
+
1374
+      }, options, function(response) {
1375
+        if (response.successful)
1376
+          publication.setDeferredStatus('succeeded');
1377
+        else
1378
+          publication.setDeferredStatus('failed', Faye.Error.parse(response.error));
1379
+      }, this);
1380
+    }, this);
1381
+
1382
+    return publication;
1383
+  },
1384
+
1385
+  _sendMessage: function(message, options, callback, context) {
1386
+    message.id = this._generateMessageId();
1387
+
1388
+    var timeout = this._advice.timeout
1389
+                ? 1.2 * this._advice.timeout / 1000
1390
+                : 1.2 * this._dispatcher.retry;
1391
+
1392
+    this.pipeThroughExtensions('outgoing', message, null, function(message) {
1393
+      if (!message) return;
1394
+      if (callback) this._responseCallbacks[message.id] = [callback, context];
1395
+      this._dispatcher.sendMessage(message, timeout, options || {});
1396
+    }, this);
1397
+  },
1398
+
1399
+  _generateMessageId: function() {
1400
+    this._messageId += 1;
1401
+    if (this._messageId >= Math.pow(2,32)) this._messageId = 0;
1402
+    return this._messageId.toString(36);
1403
+  },
1404
+
1405
+  _receiveMessage: function(message) {
1406
+    var id = message.id, callback;
1407
+
1408
+    if (message.successful !== undefined) {
1409
+      callback = this._responseCallbacks[id];
1410
+      delete this._responseCallbacks[id];
1411
+    }
1412
+
1413
+    this.pipeThroughExtensions('incoming', message, null, function(message) {
1414
+      if (!message) return;
1415
+      if (message.advice) this._handleAdvice(message.advice);
1416
+      this._deliverMessage(message);
1417
+      if (callback) callback[0].call(callback[1], message);
1418
+    }, this);
1419
+  },
1420
+
1421
+  _handleAdvice: function(advice) {
1422
+    Faye.extend(this._advice, advice);
1423
+    this._dispatcher.timeout = this._advice.timeout / 1000;
1424
+
1425
+    if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) {
1426
+      this._state = this.UNCONNECTED;
1427
+      this._dispatcher.clientId = null;
1428
+      this._cycleConnection();
1429
+    }
1430
+  },
1431
+
1432
+  _deliverMessage: function(message) {
1433
+    if (!message.channel || message.data === undefined) return;
1434
+    this.info('Client ? calling listeners for ? with ?', this._dispatcher.clientId, message.channel, message.data);
1435
+    this._channels.distributeMessage(message);
1436
+  },
1437
+
1438
+  _cycleConnection: function() {
1439
+    if (this._connectRequest) {
1440
+      this._connectRequest = null;
1441
+      this.info('Closed connection for ?', this._dispatcher.clientId);
1442
+    }
1443
+    var self = this;
1444
+    Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval);
1445
+  }
1446
+});
1447
+
1448
+Faye.extend(Faye.Client.prototype, Faye.Deferrable);
1449
+Faye.extend(Faye.Client.prototype, Faye.Publisher);
1450
+Faye.extend(Faye.Client.prototype, Faye.Logging);
1451
+Faye.extend(Faye.Client.prototype, Faye.Extensible);
1452
+
1453
+Faye.Dispatcher = Faye.Class({
1454
+  MAX_REQUEST_SIZE: 2048,
1455
+  DEFAULT_RETRY:    5,
1456
+
1457
+  UP:   1,
1458
+  DOWN: 2,
1459
+
1460
+  initialize: function(client, endpoint, options) {
1461
+    this._client     = client;
1462
+    this.endpoint    = Faye.URI.parse(endpoint);
1463
+    this._alternates = options.endpoints || {};
1464
+
1465
+    this.cookies      = Faye.Cookies && new Faye.Cookies.CookieJar();
1466
+    this._disabled    = [];
1467
+    this._envelopes   = {};
1468
+    this.headers      = {};
1469
+    this.retry        = options.retry || this.DEFAULT_RETRY;
1470
+    this._scheduler   = options.scheduler || Faye.Scheduler;
1471
+    this._state       = 0;
1472
+    this.transports   = {};
1473
+    this.wsExtensions = [];
1474
+
1475
+    this.proxy = options.proxy || {};
1476
+    if (typeof this._proxy === 'string') this._proxy = {origin: this._proxy};
1477
+
1478
+    var exts = options.websocketExtensions;
1479
+    if (exts) {
1480
+      exts = [].concat(exts);
1481
+      for (var i = 0, n = exts.length; i < n; i++)
1482
+        this.addWebsocketExtension(exts[i]);
1483
+    }
1484
+
1485
+    this.tls = options.tls || {};
1486
+    this.tls.ca = this.tls.ca || options.ca;
1487
+
1488
+    for (var type in this._alternates)
1489
+      this._alternates[type] = Faye.URI.parse(this._alternates[type]);
1490
+
1491
+    this.maxRequestSize = this.MAX_REQUEST_SIZE;
1492
+  },
1493
+
1494
+  endpointFor: function(connectionType) {
1495
+    return this._alternates[connectionType] || this.endpoint;
1496
+  },
1497
+
1498
+  addWebsocketExtension: function(extension) {
1499
+    this.wsExtensions.push(extension);
1500
+  },
1501
+
1502
+  disable: function(feature) {
1503
+    this._disabled.push(feature);
1504
+  },
1505
+
1506
+  setHeader: function(name, value) {
1507
+    this.headers[name] = value;
1508
+  },
1509
+
1510
+  close: function() {
1511
+    var transport = this._transport;
1512
+    delete this._transport;
1513
+    if (transport) transport.close();
1514
+  },
1515
+
1516
+  getConnectionTypes: function() {
1517
+    return Faye.Transport.getConnectionTypes();
1518
+  },
1519
+
1520
+  selectTransport: function(transportTypes) {
1521
+    Faye.Transport.get(this, transportTypes, this._disabled, function(transport) {
1522
+      this.debug('Selected ? transport for ?', transport.connectionType, Faye.URI.stringify(transport.endpoint));
1523
+
1524
+      if (transport === this._transport) return;
1525
+      if (this._transport) this._transport.close();
1526
+
1527
+      this._transport = transport;
1528
+      this.connectionType = transport.connectionType;
1529
+    }, this);
1530
+  },
1531
+
1532
+  sendMessage: function(message, timeout, options) {
1533
+    options = options || {};
1534
+
1535
+    var id       = message.id,
1536
+        attempts = options.attempts,
1537
+        deadline = options.deadline && new Date().getTime() + (options.deadline * 1000),
1538
+        envelope = this._envelopes[id],
1539
+        scheduler;
1540
+
1541
+    if (!envelope) {
1542
+      scheduler = new this._scheduler(message, {timeout: timeout, interval: this.retry, attempts: attempts, deadline: deadline});
1543
+      envelope  = this._envelopes[id] = {message: message, scheduler: scheduler};
1544
+    }
1545
+
1546
+    this._sendEnvelope(envelope);
1547
+  },
1548
+
1549
+  _sendEnvelope: function(envelope) {
1550
+    if (!this._transport) return;
1551
+    if (envelope.request || envelope.timer) return;
1552
+
1553
+    var message   = envelope.message,
1554
+        scheduler = envelope.scheduler,
1555
+        self      = this;
1556
+
1557
+    if (!scheduler.isDeliverable()) {
1558
+      scheduler.abort();
1559
+      delete this._envelopes[message.id];
1560
+      return;
1561
+    }
1562
+
1563
+    envelope.timer = Faye.ENV.setTimeout(function() {
1564
+      self.handleError(message);
1565
+    }, scheduler.getTimeout() * 1000);
1566
+
1567
+    scheduler.send();
1568
+    envelope.request = this._transport.sendMessage(message);
1569
+  },
1570
+
1571
+  handleResponse: function(reply) {
1572
+    var envelope = this._envelopes[reply.id];
1573
+
1574
+    if (reply.successful !== undefined && envelope) {
1575
+      envelope.scheduler.succeed();
1576
+      delete this._envelopes[reply.id];
1577
+      Faye.ENV.clearTimeout(envelope.timer);
1578
+    }
1579
+
1580
+    this.trigger('message', reply);
1581
+
1582
+    if (this._state === this.UP) return;
1583
+    this._state = this.UP;
1584
+    this._client.trigger('transport:up');
1585
+  },
1586
+
1587
+  handleError: function(message, immediate) {
1588
+    var envelope = this._envelopes[message.id],
1589
+        request  = envelope && envelope.request,
1590
+        self     = this;
1591
+
1592
+    if (!request) return;
1593
+
1594
+    request.then(function(req) {
1595
+      if (req && req.abort) req.abort();
1596
+    });
1597
+
1598
+    var scheduler = envelope.scheduler;
1599
+    scheduler.fail();
1600
+
1601
+    Faye.ENV.clearTimeout(envelope.timer);
1602
+    envelope.request = envelope.timer = null;
1603
+
1604
+    if (immediate) {
1605
+      this._sendEnvelope(envelope);
1606
+    } else {
1607
+      envelope.timer = Faye.ENV.setTimeout(function() {
1608
+        envelope.timer = null;
1609
+        self._sendEnvelope(envelope);
1610
+      }, scheduler.getInterval() * 1000);
1611
+    }
1612
+
1613
+    if (this._state === this.DOWN) return;
1614
+    this._state = this.DOWN;
1615
+    this._client.trigger('transport:down');
1616
+  }
1617
+});
1618
+
1619
+Faye.extend(Faye.Dispatcher.prototype, Faye.Publisher);
1620
+Faye.extend(Faye.Dispatcher.prototype, Faye.Logging);
1621
+
1622
+Faye.Scheduler = function(message, options) {
1623
+  this.message  = message;
1624
+  this.options  = options;
1625
+  this.attempts = 0;
1626
+};
1627
+
1628
+Faye.extend(Faye.Scheduler.prototype, {
1629
+  getTimeout: function() {
1630
+    return this.options.timeout;
1631
+  },
1632
+
1633
+  getInterval: function() {
1634
+    return this.options.interval;
1635
+  },
1636
+
1637
+  isDeliverable: function() {
1638
+    var attempts = this.options.attempts,
1639
+        made     = this.attempts,
1640
+        deadline = this.options.deadline,
1641
+        now      = new Date().getTime();
1642
+
1643
+    if (attempts !== undefined && made >= attempts)
1644
+      return false;
1645
+
1646
+    if (deadline !== undefined && now > deadline)
1647
+      return false;
1648
+
1649
+    return true;
1650
+  },
1651
+
1652
+  send: function() {
1653
+    this.attempts += 1;
1654
+  },
1655
+
1656
+  succeed: function() {},
1657
+
1658
+  fail: function() {},
1659
+
1660
+  abort: function() {}
1661
+});
1662
+
1663
+Faye.Transport = Faye.extend(Faye.Class({
1664
+  DEFAULT_PORTS:    {'http:': 80, 'https:': 443, 'ws:': 80, 'wss:': 443},
1665
+  SECURE_PROTOCOLS: ['https:', 'wss:'],
1666
+  MAX_DELAY:        0,
1667
+
1668
+  batching:  true,
1669
+
1670
+  initialize: function(dispatcher, endpoint) {
1671
+    this._dispatcher = dispatcher;
1672
+    this.endpoint    = endpoint;
1673
+    this._outbox     = [];
1674
+    this._proxy      = Faye.extend({}, this._dispatcher.proxy);
1675
+
1676
+    if (!this._proxy.origin && Faye.NodeAdapter) {
1677
+      this._proxy.origin = Faye.indexOf(this.SECURE_PROTOCOLS, this.endpoint.protocol) >= 0
1678
+                         ? (process.env.HTTPS_PROXY || process.env.https_proxy)
1679
+                         : (process.env.HTTP_PROXY  || process.env.http_proxy);
1680
+    }
1681
+  },
1682
+
1683
+  close: function() {},
1684
+
1685
+  encode: function(messages) {
1686
+    return '';
1687
+  },
1688
+
1689
+  sendMessage: function(message) {
1690
+    this.debug('Client ? sending message to ?: ?',
1691
+               this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), message);
1692
+
1693
+    if (!this.batching) return Faye.Promise.fulfilled(this.request([message]));
1694
+
1695
+    this._outbox.push(message);
1696
+    this._flushLargeBatch();
1697
+    this._promise = this._promise || new Faye.Promise();
1698
+
1699
+    if (message.channel === Faye.Channel.HANDSHAKE) {
1700
+      this.addTimeout('publish', 0.01, this._flush, this);
1701
+      return this._promise;
1702
+    }
1703
+
1704
+    if (message.channel === Faye.Channel.CONNECT)
1705
+      this._connectMessage = message;
1706
+
1707
+    this.addTimeout('publish', this.MAX_DELAY, this._flush, this);
1708
+    return this._promise;
1709
+  },
1710
+
1711
+  _flush: function() {
1712
+    this.removeTimeout('publish');
1713
+
1714
+    if (this._outbox.length > 1 && this._connectMessage)
1715
+      this._connectMessage.advice = {timeout: 0};
1716
+
1717
+    Faye.Promise.fulfill(this._promise, this.request(this._outbox));
1718
+    delete this._promise;
1719
+
1720
+    this._connectMessage = null;
1721
+    this._outbox = [];
1722
+  },
1723
+
1724
+  _flushLargeBatch: function() {
1725
+    var string = this.encode(this._outbox);
1726
+    if (string.length < this._dispatcher.maxRequestSize) return;
1727
+    var last = this._outbox.pop();
1728
+    this._flush();
1729
+    if (last) this._outbox.push(last);
1730
+  },
1731
+
1732
+  _receive: function(replies) {
1733
+    if (!replies) return;
1734
+    replies = [].concat(replies);
1735
+
1736
+    this.debug('Client ? received from ? via ?: ?',
1737
+               this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), this.connectionType, replies);
1738
+
1739
+    for (var i = 0, n = replies.length; i < n; i++)
1740
+      this._dispatcher.handleResponse(replies[i]);
1741
+  },
1742
+
1743
+  _handleError: function(messages, immediate) {
1744
+    messages = [].concat(messages);
1745
+
1746
+    this.debug('Client ? failed to send to ? via ?: ?',
1747
+               this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), this.connectionType, messages);
1748
+
1749
+    for (var i = 0, n = messages.length; i < n; i++)
1750
+      this._dispatcher.handleError(messages[i]);
1751
+  },
1752
+
1753
+  _getCookies: function() {
1754
+    var cookies = this._dispatcher.cookies,
1755
+        url     = Faye.URI.stringify(this.endpoint);
1756
+
1757
+    if (!cookies) return '';
1758
+
1759
+    return Faye.map(cookies.getCookiesSync(url), function(cookie) {
1760
+      return cookie.cookieString();
1761
+    }).join('; ');
1762
+  },
1763
+
1764
+  _storeCookies: function(setCookie) {
1765
+    var cookies = this._dispatcher.cookies,
1766
+        url     = Faye.URI.stringify(this.endpoint),
1767
+        cookie;
1768
+
1769
+    if (!setCookie || !cookies) return;
1770
+    setCookie = [].concat(setCookie);
1771
+
1772
+    for (var i = 0, n = setCookie.length; i < n; i++) {
1773
+      cookie = Faye.Cookies.Cookie.parse(setCookie[i]);
1774
+      cookies.setCookieSync(cookie, url);
1775
+    }
1776
+  }
1777
+
1778
+}), {
1779
+  get: function(dispatcher, allowed, disabled, callback, context) {
1780
+    var endpoint = dispatcher.endpoint;
1781
+
1782
+    Faye.asyncEach(this._transports, function(pair, resume) {
1783
+      var connType     = pair[0], klass = pair[1],
1784
+          connEndpoint = dispatcher.endpointFor(connType);
1785
+
1786
+      if (Faye.indexOf(disabled, connType) >= 0)
1787
+        return resume();
1788
+
1789
+      if (Faye.indexOf(allowed, connType) < 0) {
1790
+        klass.isUsable(dispatcher, connEndpoint, function() {});
1791
+        return resume();
1792
+      }
1793
+
1794
+      klass.isUsable(dispatcher, connEndpoint, function(isUsable) {
1795
+        if (!isUsable) return resume();
1796
+        var transport = klass.hasOwnProperty('create') ? klass.create(dispatcher, connEndpoint) : new klass(dispatcher, connEndpoint);
1797
+        callback.call(context, transport);
1798
+      });
1799
+    }, function() {
1800
+      throw new Error('Could not find a usable connection type for ' + Faye.URI.stringify(endpoint));
1801
+    });
1802
+  },
1803
+
1804
+  register: function(type, klass) {
1805
+    this._transports.push([type, klass]);
1806
+    klass.prototype.connectionType = type;
1807
+  },
1808
+
1809
+  getConnectionTypes: function() {
1810
+    return Faye.map(this._transports, function(t) { return t[0] });
1811
+  },
1812
+
1813
+  _transports: []
1814
+});
1815
+
1816
+Faye.extend(Faye.Transport.prototype, Faye.Logging);
1817
+Faye.extend(Faye.Transport.prototype, Faye.Timeouts);
1818
+
1819
+Faye.Event = {
1820
+  _registry: [],
1821
+
1822
+  on: function(element, eventName, callback, context) {
1823
+    var wrapped = function() { callback.call(context) };
1824
+
1825
+    if (element.addEventListener)
1826
+      element.addEventListener(eventName, wrapped, false);
1827
+    else
1828
+      element.attachEvent('on' + eventName, wrapped);
1829
+
1830
+    this._registry.push({
1831
+      _element:   element,
1832
+      _type:      eventName,
1833
+      _callback:  callback,
1834
+      _context:     context,
1835
+      _handler:   wrapped
1836
+    });
1837
+  },
1838
+
1839
+  detach: function(element, eventName, callback, context) {
1840
+    var i = this._registry.length, register;
1841
+    while (i--) {
1842
+      register = this._registry[i];
1843
+
1844
+      if ((element    && element    !== register._element)   ||
1845
+          (eventName  && eventName  !== register._type)      ||
1846
+          (callback   && callback   !== register._callback)  ||
1847
+          (context      && context      !== register._context))
1848
+        continue;
1849
+
1850
+      if (register._element.removeEventListener)
1851
+        register._element.removeEventListener(register._type, register._handler, false);
1852
+      else
1853
+        register._element.detachEvent('on' + register._type, register._handler);
1854
+
1855
+      this._registry.splice(i,1);
1856
+      register = null;
1857
+    }
1858
+  }
1859
+};
1860
+
1861
+if (Faye.ENV.onunload !== undefined) Faye.Event.on(Faye.ENV, 'unload', Faye.Event.detach, Faye.Event);
1862
+
1863
+/*
1864
+    json2.js
1865
+    2013-05-26
1866
+
1867
+    Public Domain.
1868
+
1869
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
1870
+
1871
+    See http://www.JSON.org/js.html
1872
+
1873
+
1874
+    This code should be minified before deployment.
1875
+    See http://javascript.crockford.com/jsmin.html
1876
+
1877
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
1878
+    NOT CONTROL.
1879
+
1880
+
1881
+    This file creates a global JSON object containing two methods: stringify
1882
+    and parse.
1883
+
1884
+        JSON.stringify(value, replacer, space)
1885
+            value       any JavaScript value, usually an object or array.
1886
+
1887
+            replacer    an optional parameter that determines how object
1888
+                        values are stringified for objects. It can be a
1889
+                        function or an array of strings.
1890
+
1891
+            space       an optional parameter that specifies the indentation
1892
+                        of nested structures. If it is omitted, the text will
1893
+                        be packed without extra whitespace. If it is a number,
1894
+                        it will specify the number of spaces to indent at each
1895
+                        level. If it is a string (such as '\t' or '&nbsp;'),
1896
+                        it contains the characters used to indent at each level.
1897
+
1898
+            This method produces a JSON text from a JavaScript value.
1899
+
1900
+            When an object value is found, if the object contains a toJSON
1901
+            method, its toJSON method will be called and the result will be
1902
+            stringified. A toJSON method does not serialize: it returns the
1903
+            value represented by the name/value pair that should be serialized,
1904
+            or undefined if nothing should be serialized. The toJSON method
1905
+            will be passed the key associated with the value, and this will be
1906
+            bound to the value
1907
+
1908
+            For example, this would serialize Dates as ISO strings.
1909
+
1910
+                Date.prototype.toJSON = function (key) {
1911
+                    function f(n) {
1912
+                        // Format integers to have at least two digits.
1913
+                        return n < 10 ? '0' + n : n;
1914
+                    }
1915
+
1916
+                    return this.getUTCFullYear()   + '-' +
1917
+                         f(this.getUTCMonth() + 1) + '-' +
1918
+                         f(this.getUTCDate())      + 'T' +
1919
+                         f(this.getUTCHours())     + ':' +
1920
+                         f(this.getUTCMinutes())   + ':' +
1921
+                         f(this.getUTCSeconds())   + 'Z';
1922
+                };
1923
+
1924
+            You can provide an optional replacer method. It will be passed the
1925
+            key and value of each member, with this bound to the containing
1926
+            object. The value that is returned from your method will be
1927
+            serialized. If your method returns undefined, then the member will
1928
+            be excluded from the serialization.
1929
+
1930
+            If the replacer parameter is an array of strings, then it will be
1931
+            used to select the members to be serialized. It filters the results
1932
+            such that only members with keys listed in the replacer array are
1933
+            stringified.
1934
+
1935
+            Values that do not have JSON representations, such as undefined or
1936
+            functions, will not be serialized. Such values in objects will be
1937
+            dropped; in arrays they will be replaced with null. You can use
1938
+            a replacer function to replace those with JSON values.
1939
+            JSON.stringify(undefined) returns undefined.
1940
+
1941
+            The optional space parameter produces a stringification of the
1942
+            value that is filled with line breaks and indentation to make it
1943
+            easier to read.
1944
+
1945
+            If the space parameter is a non-empty string, then that string will
1946
+            be used for indentation. If the space parameter is a number, then
1947
+            the indentation will be that many spaces.
1948
+
1949
+            Example:
1950
+
1951
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
1952
+            // text is '["e",{"pluribus":"unum"}]'
1953
+
1954
+
1955
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
1956
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
1957
+
1958
+            text = JSON.stringify([new Date()], function (key, value) {
1959
+                return this[key] instanceof Date ?
1960
+                    'Date(' + this[key] + ')' : value;
1961
+            });
1962
+            // text is '["Date(---current time---)"]'
1963
+
1964
+
1965
+        JSON.parse(text, reviver)
1966
+            This method parses a JSON text to produce an object or array.
1967
+            It can throw a SyntaxError exception.
1968
+
1969
+            The optional reviver parameter is a function that can filter and
1970
+            transform the results. It receives each of the keys and values,
1971
+            and its return value is used instead of the original value.
1972
+            If it returns what it received, then the structure is not modified.
1973
+            If it returns undefined then the member is deleted.
1974
+
1975
+            Example:
1976
+
1977
+            // Parse the text. Values that look like ISO date strings will
1978
+            // be converted to Date objects.
1979
+
1980
+            myData = JSON.parse(text, function (key, value) {
1981
+                var a;
1982
+                if (typeof value === 'string') {
1983
+                    a =
1984
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
1985
+                    if (a) {
1986
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
1987
+                            +a[5], +a[6]));
1988
+                    }
1989
+                }
1990
+                return value;
1991
+            });
1992
+
1993
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
1994
+                var d;
1995
+                if (typeof value === 'string' &&
1996
+                        value.slice(0, 5) === 'Date(' &&
1997
+                        value.slice(-1) === ')') {
1998
+                    d = new Date(value.slice(5, -1));
1999
+                    if (d) {
2000
+                        return d;
2001
+                    }
2002
+                }
2003
+                return value;
2004
+            });
2005
+
2006
+
2007
+    This is a reference implementation. You are free to copy, modify, or
2008
+    redistribute.
2009
+*/
2010
+
2011
+/*jslint evil: true, regexp: true */
2012
+
2013
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
2014
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
2015
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
2016
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
2017
+    test, toJSON, toString, valueOf
2018
+*/
2019
+
2020
+
2021
+// Create a JSON object only if one does not already exist. We create the
2022
+// methods in a closure to avoid creating global variables.
2023
+
2024
+if (typeof JSON !== 'object') {
2025
+    JSON = {};
2026
+}
2027
+
2028
+(function () {
2029
+    'use strict';
2030
+
2031
+    function f(n) {
2032
+        // Format integers to have at least two digits.
2033
+        return n < 10 ? '0' + n : n;
2034
+    }
2035
+
2036
+    if (typeof Date.prototype.toJSON !== 'function') {
2037
+
2038
+        Date.prototype.toJSON = function () {
2039
+
2040
+            return isFinite(this.valueOf())
2041
+                ? this.getUTCFullYear()     + '-' +
2042
+                    f(this.getUTCMonth() + 1) + '-' +
2043
+                    f(this.getUTCDate())      + 'T' +
2044
+                    f(this.getUTCHours())     + ':' +
2045
+                    f(this.getUTCMinutes())   + ':' +
2046
+                    f(this.getUTCSeconds())   + 'Z'
2047
+                : null;
2048
+        };
2049
+
2050
+        String.prototype.toJSON      =
2051
+            Number.prototype.toJSON  =
2052
+            Boolean.prototype.toJSON = function () {
2053
+                return this.valueOf();
2054
+            };
2055
+    }
2056
+
2057
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
2058
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
2059
+        gap,
2060
+        indent,
2061
+        meta = {    // table of character substitutions
2062
+            '\b': '\\b',
2063
+            '\t': '\\t',
2064
+            '\n': '\\n',
2065
+            '\f': '\\f',
2066
+            '\r': '\\r',
2067
+            '"' : '\\"',
2068
+            '\\': '\\\\'
2069
+        },
2070
+        rep;
2071
+
2072
+
2073
+    function quote(string) {
2074
+
2075
+// If the string contains no control characters, no quote characters, and no
2076
+// backslash characters, then we can safely slap some quotes around it.
2077
+// Otherwise we must also replace the offending characters with safe escape
2078
+// sequences.
2079
+
2080
+        escapable.lastIndex = 0;
2081
+        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
2082
+            var c = meta[a];
2083
+            return typeof c === 'string'
2084
+                ? c
2085
+                : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
2086
+        }) + '"' : '"' + string + '"';
2087
+    }
2088
+
2089
+
2090
+    function str(key, holder) {
2091
+
2092
+// Produce a string from holder[key].
2093
+
2094
+        var i,          // The loop counter.
2095
+            k,          // The member key.
2096
+            v,          // The member value.
2097
+            length,
2098
+            mind = gap,
2099
+            partial,
2100
+            value = holder[key];
2101
+
2102
+// If the value has a toJSON method, call it to obtain a replacement value.
2103
+
2104
+        if (value && typeof value === 'object' &&
2105
+                typeof value.toJSON === 'function') {
2106
+            value = value.toJSON(key);
2107
+        }
2108
+
2109
+// If we were called with a replacer function, then call the replacer to
2110
+// obtain a replacement value.
2111
+
2112
+        if (typeof rep === 'function') {
2113
+            value = rep.call(holder, key, value);
2114
+        }
2115
+
2116
+// What happens next depends on the value's type.
2117
+
2118
+        switch (typeof value) {
2119
+        case 'string':
2120
+            return quote(value);
2121
+
2122
+        case 'number':
2123
+
2124
+// JSON numbers must be finite. Encode non-finite numbers as null.
2125
+
2126
+            return isFinite(value) ? String(value) : 'null';
2127
+
2128
+        case 'boolean':
2129
+        case 'null':
2130
+
2131
+// If the value is a boolean or null, convert it to a string. Note:
2132
+// typeof null does not produce 'null'. The case is included here in
2133
+// the remote chance that this gets fixed someday.
2134
+
2135
+            return String(value);
2136
+
2137
+// If the type is 'object', we might be dealing with an object or an array or
2138
+// null.
2139
+
2140
+        case 'object':
2141
+
2142
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
2143
+// so watch out for that case.
2144
+
2145
+            if (!value) {
2146
+                return 'null';
2147
+            }
2148
+
2149
+// Make an array to hold the partial results of stringifying this object value.
2150
+
2151
+            gap += indent;
2152
+            partial = [];
2153
+
2154
+// Is the value an array?
2155
+
2156
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
2157
+
2158
+// The value is an array. Stringify every element. Use null as a placeholder
2159
+// for non-JSON values.
2160
+
2161
+                length = value.length;
2162
+                for (i = 0; i < length; i += 1) {
2163
+                    partial[i] = str(i, value) || 'null';
2164
+                }
2165
+
2166
+// Join all of the elements together, separated with commas, and wrap them in
2167
+// brackets.
2168
+
2169
+                v = partial.length === 0
2170
+                    ? '[]'
2171
+                    : gap
2172
+                    ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
2173
+                    : '[' + partial.join(',') + ']';
2174
+                gap = mind;
2175
+                return v;
2176
+            }
2177
+
2178
+// If the replacer is an array, use it to select the members to be stringified.
2179
+
2180
+            if (rep && typeof rep === 'object') {
2181
+                length = rep.length;
2182
+                for (i = 0; i < length; i += 1) {
2183
+                    if (typeof rep[i] === 'string') {
2184
+                        k = rep[i];
2185
+                        v = str(k, value);
2186
+                        if (v) {
2187
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
2188
+                        }
2189
+                    }
2190
+                }
2191
+            } else {
2192
+
2193
+// Otherwise, iterate through all of the keys in the object.
2194
+
2195
+                for (k in value) {
2196
+                    if (Object.prototype.hasOwnProperty.call(value, k)) {
2197
+                        v = str(k, value);
2198
+                        if (v) {
2199
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
2200
+                        }
2201
+                    }
2202
+                }
2203
+            }
2204
+
2205
+// Join all of the member texts together, separated with commas,
2206
+// and wrap them in braces.
2207
+
2208
+            v = partial.length === 0
2209
+                ? '{}'
2210
+                : gap
2211
+                ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
2212
+                : '{' + partial.join(',') + '}';
2213
+            gap = mind;
2214
+            return v;
2215
+        }
2216
+    }
2217
+
2218
+// If the JSON object does not yet have a stringify method, give it one.
2219
+
2220
+    Faye.stringify = function (value, replacer, space) {
2221
+
2222
+// The stringify method takes a value and an optional replacer, and an optional
2223
+// space parameter, and returns a JSON text. The replacer can be a function
2224
+// that can replace values, or an array of strings that will select the keys.
2225
+// A default replacer method can be provided. Use of the space parameter can
2226
+// produce text that is more easily readable.
2227
+
2228
+        var i;
2229
+        gap = '';
2230
+        indent = '';
2231
+
2232
+// If the space parameter is a number, make an indent string containing that
2233
+// many spaces.
2234
+
2235
+        if (typeof space === 'number') {
2236
+            for (i = 0; i < space; i += 1) {
2237
+                indent += ' ';
2238
+            }
2239
+
2240
+// If the space parameter is a string, it will be used as the indent string.
2241
+
2242
+        } else if (typeof space === 'string') {
2243
+            indent = space;
2244
+        }
2245
+
2246
+// If there is a replacer, it must be a function or an array.
2247
+// Otherwise, throw an error.
2248
+
2249
+        rep = replacer;
2250
+        if (replacer && typeof replacer !== 'function' &&
2251
+                (typeof replacer !== 'object' ||
2252
+                typeof replacer.length !== 'number')) {
2253
+            throw new Error('JSON.stringify');
2254
+        }
2255
+
2256
+// Make a fake root object containing our value under the key of ''.
2257
+// Return the result of stringifying the value.
2258
+
2259
+        return str('', {'': value});
2260
+    };
2261
+
2262
+    if (typeof JSON.stringify !== 'function') {
2263
+        JSON.stringify = Faye.stringify;
2264
+    }
2265
+
2266
+// If the JSON object does not yet have a parse method, give it one.
2267
+
2268
+    if (typeof JSON.parse !== 'function') {
2269
+        JSON.parse = function (text, reviver) {
2270
+
2271
+// The parse method takes a text and an optional reviver function, and returns
2272
+// a JavaScript value if the text is a valid JSON text.
2273
+
2274
+            var j;
2275
+
2276
+            function walk(holder, key) {
2277
+
2278
+// The walk method is used to recursively walk the resulting structure so
2279
+// that modifications can be made.
2280
+
2281
+                var k, v, value = holder[key];
2282
+                if (value && typeof value === 'object') {
2283
+                    for (k in value) {
2284
+                        if (Object.prototype.hasOwnProperty.call(value, k)) {
2285
+                            v = walk(value, k);
2286
+                            if (v !== undefined) {
2287
+                                value[k] = v;
2288
+                            } else {
2289
+                                delete value[k];
2290
+                            }
2291
+                        }
2292
+                    }
2293
+                }
2294
+                return reviver.call(holder, key, value);
2295
+            }
2296
+
2297
+
2298
+// Parsing happens in four stages. In the first stage, we replace certain
2299
+// Unicode characters with escape sequences. JavaScript handles many characters
2300
+// incorrectly, either silently deleting them, or treating them as line endings.
2301
+
2302
+            text = String(text);
2303
+            cx.lastIndex = 0;
2304
+            if (cx.test(text)) {
2305
+                text = text.replace(cx, function (a) {
2306
+                    return '\\u' +
2307
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
2308
+                });
2309
+            }
2310
+
2311
+// In the second stage, we run the text against regular expressions that look
2312
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
2313
+// because they can cause invocation, and '=' because it can cause mutation.
2314
+// But just to be safe, we want to reject all unexpected forms.
2315
+
2316
+// We split the second stage into 4 regexp operations in order to work around
2317
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
2318
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
2319
+// replace all simple value tokens with ']' characters. Third, we delete all
2320
+// open brackets that follow a colon or comma or that begin the text. Finally,
2321
+// we look to see that the remaining characters are only whitespace or ']' or
2322
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
2323
+
2324
+            if (/^[\],:{}\s]*$/
2325
+                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
2326
+                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
2327
+                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
2328
+
2329
+// In the third stage we use the eval function to compile the text into a
2330
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
2331
+// in JavaScript: it can begin a block or an object literal. We wrap the text
2332
+// in parens to eliminate the ambiguity.
2333
+
2334
+                j = eval('(' + text + ')');
2335
+
2336
+// In the optional fourth stage, we recursively walk the new structure, passing
2337
+// each name/value pair to a reviver function for possible transformation.
2338
+
2339
+                return typeof reviver === 'function'
2340
+                    ? walk({'': j}, '')
2341
+                    : j;
2342
+            }
2343
+
2344
+// If the text is not JSON parseable, then a SyntaxError is thrown.
2345
+
2346
+            throw new SyntaxError('JSON.parse');
2347
+        };
2348
+    }
2349
+}());
2350
+
2351
+Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
2352
+  UNCONNECTED:  1,
2353
+  CONNECTING:   2,
2354
+  CONNECTED:    3,
2355
+
2356
+  batching:     false,
2357
+
2358
+  isUsable: function(callback, context) {
2359
+    this.callback(function() { callback.call(context, true) });
2360
+    this.errback(function() { callback.call(context, false) });
2361
+    this.connect();
2362
+  },
2363
+
2364
+  request: function(messages) {
2365
+    this._pending = this._pending || new Faye.Set();
2366
+    for (var i = 0, n = messages.length; i < n; i++) this._pending.add(messages[i]);
2367
+
2368
+    var promise = new Faye.Promise();
2369
+
2370
+    this.callback(function(socket) {
2371
+      if (!socket) return;
2372
+      socket.send(Faye.toJSON(messages));
2373
+      Faye.Promise.fulfill(promise, socket);
2374
+    }, this);
2375
+
2376
+    this.connect();
2377
+
2378
+    return {
2379
+      abort: function() { promise.then(function(ws) { ws.close() }) }
2380
+    };
2381
+  },
2382
+
2383
+  connect: function() {
2384
+    if (Faye.Transport.WebSocket._unloaded) return;
2385
+
2386
+    this._state = this._state || this.UNCONNECTED;
2387
+    if (this._state !== this.UNCONNECTED) return;
2388
+    this._state = this.CONNECTING;
2389
+
2390
+    var socket = this._createSocket();
2391
+    if (!socket) return this.setDeferredStatus('failed');
2392
+
2393
+    var self = this;
2394
+
2395
+    socket.onopen = function() {
2396
+      if (socket.headers) self._storeCookies(socket.headers['set-cookie']);
2397
+      self._socket = socket;
2398
+      self._state = self.CONNECTED;
2399
+      self._everConnected = true;
2400
+      self._ping();
2401
+      self.setDeferredStatus('succeeded', socket);
2402
+    };
2403
+
2404
+    var closed = false;
2405
+    socket.onclose = socket.onerror = function() {
2406
+      if (closed) return;
2407
+      closed = true;
2408
+
2409
+      var wasConnected = (self._state === self.CONNECTED);
2410
+      socket.onopen = socket.onclose = socket.onerror = socket.onmessage = null;
2411
+
2412
+      delete self._socket;
2413
+      self._state = self.UNCONNECTED;
2414
+      self.removeTimeout('ping');
2415
+      self.setDeferredStatus('unknown');
2416
+
2417
+      var pending = self._pending ? self._pending.toArray() : [];
2418
+      delete self._pending;
2419
+
2420
+      if (wasConnected) {
2421
+        self._handleError(pending, true);
2422
+      } else if (self._everConnected) {
2423
+        self._handleError(pending);
2424
+      } else {
2425
+        self.setDeferredStatus('failed');
2426
+      }
2427
+    };
2428
+
2429
+    socket.onmessage = function(event) {
2430
+      var replies = JSON.parse(event.data);
2431
+      if (!replies) return;
2432
+
2433
+      replies = [].concat(replies);
2434
+
2435
+      for (var i = 0, n = replies.length; i < n; i++) {
2436
+        if (replies[i].successful === undefined) continue;
2437
+        self._pending.remove(replies[i]);
2438
+      }
2439
+      self._receive(replies);
2440
+    };
2441
+  },
2442
+
2443
+  close: function() {
2444
+    if (!this._socket) return;
2445
+    this._socket.close();
2446
+  },
2447
+
2448
+  _createSocket: function() {
2449
+    var url        = Faye.Transport.WebSocket.getSocketUrl(this.endpoint),
2450
+        headers    = this._dispatcher.headers,
2451
+        extensions = this._dispatcher.wsExtensions,
2452
+        cookie     = this._getCookies(),
2453
+        tls        = this._dispatcher.tls,
2454
+        options    = {extensions: extensions, headers: headers, proxy: this._proxy, tls: tls};
2455
+
2456
+    if (cookie !== '') options.headers['Cookie'] = cookie;
2457
+
2458
+    if (Faye.WebSocket)        return new Faye.WebSocket.Client(url, [], options);
2459
+    if (Faye.ENV.MozWebSocket) return new MozWebSocket(url);
2460
+    if (Faye.ENV.WebSocket)    return new WebSocket(url);
2461
+  },
2462
+
2463
+  _ping: function() {
2464
+    if (!this._socket) return;
2465
+    this._socket.send('[]');
2466
+    this.addTimeout('ping', this._dispatcher.timeout / 2, this._ping, this);
2467
+  }
2468
+
2469
+}), {
2470
+  PROTOCOLS: {
2471
+    'http:':  'ws:',
2472
+    'https:': 'wss:'
2473
+  },
2474
+
2475
+  create: function(dispatcher, endpoint) {
2476
+    var sockets = dispatcher.transports.websocket = dispatcher.transports.websocket || {};
2477
+    sockets[endpoint.href] = sockets[endpoint.href] || new this(dispatcher, endpoint);
2478
+    return sockets[endpoint.href];
2479
+  },
2480
+
2481
+  getSocketUrl: function(endpoint) {
2482
+    endpoint = Faye.copyObject(endpoint);
2483
+    endpoint.protocol = this.PROTOCOLS[endpoint.protocol];
2484
+    return Faye.URI.stringify(endpoint);
2485
+  },
2486
+
2487
+  isUsable: function(dispatcher, endpoint, callback, context) {
2488
+    this.create(dispatcher, endpoint).isUsable(callback, context);
2489
+  }
2490
+});
2491
+
2492
+Faye.extend(Faye.Transport.WebSocket.prototype, Faye.Deferrable);
2493
+Faye.Transport.register('websocket', Faye.Transport.WebSocket);
2494
+
2495
+if (Faye.Event && Faye.ENV.onbeforeunload !== undefined)
2496
+  Faye.Event.on(Faye.ENV, 'beforeunload', function() {
2497
+    Faye.Transport.WebSocket._unloaded = true;
2498
+  });
2499
+
2500
+Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, {
2501
+  initialize: function(dispatcher, endpoint) {
2502
+    Faye.Transport.prototype.initialize.call(this, dispatcher, endpoint);
2503
+    if (!Faye.ENV.EventSource) return this.setDeferredStatus('failed');
2504
+
2505
+    this._xhr = new Faye.Transport.XHR(dispatcher, endpoint);
2506
+
2507
+    endpoint = Faye.copyObject(endpoint);
2508
+    endpoint.pathname += '/' + dispatcher.clientId;
2509
+
2510
+    var socket = new EventSource(Faye.URI.stringify(endpoint)),
2511
+        self   = this;
2512
+
2513
+    socket.onopen = function() {
2514
+      self._everConnected = true;
2515
+      self.setDeferredStatus('succeeded');
2516
+    };
2517
+
2518
+    socket.onerror = function() {
2519
+      if (self._everConnected) {
2520
+        self._handleError([]);
2521
+      } else {
2522
+        self.setDeferredStatus('failed');
2523
+        socket.close();
2524
+      }
2525
+    };
2526
+
2527
+    socket.onmessage = function(event) {
2528
+      self._receive(JSON.parse(event.data));
2529
+    };
2530
+
2531
+    this._socket = socket;
2532
+  },
2533
+
2534
+  close: function() {
2535
+    if (!this._socket) return;
2536
+    this._socket.onopen = this._socket.onerror = this._socket.onmessage = null;
2537
+    this._socket.close();
2538
+    delete this._socket;
2539
+  },
2540
+
2541
+  isUsable: function(callback, context) {
2542
+    this.callback(function() { callback.call(context, true) });
2543
+    this.errback(function() { callback.call(context, false) });
2544
+  },
2545
+
2546
+  encode: function(messages) {
2547
+    return this._xhr.encode(messages);
2548
+  },
2549
+
2550
+  request: function(messages) {
2551
+    return this._xhr.request(messages);
2552
+  }
2553
+
2554
+}), {
2555
+  isUsable: function(dispatcher, endpoint, callback, context) {
2556
+    var id = dispatcher.clientId;
2557
+    if (!id) return callback.call(context, false);
2558
+
2559
+    Faye.Transport.XHR.isUsable(dispatcher, endpoint, function(usable) {
2560
+      if (!usable) return callback.call(context, false);
2561
+      this.create(dispatcher, endpoint).isUsable(callback, context);
2562
+    }, this);
2563
+  },
2564
+
2565
+  create: function(dispatcher, endpoint) {
2566
+    var sockets = dispatcher.transports.eventsource = dispatcher.transports.eventsource || {},
2567
+        id      = dispatcher.clientId;
2568
+
2569
+    var url = Faye.copyObject(endpoint);
2570
+    url.pathname += '/' + (id || '');
2571
+    url = Faye.URI.stringify(url);
2572
+
2573
+    sockets[url] = sockets[url] || new this(dispatcher, endpoint);
2574
+    return sockets[url];
2575
+  }
2576
+});
2577
+
2578
+Faye.extend(Faye.Transport.EventSource.prototype, Faye.Deferrable);
2579
+Faye.Transport.register('eventsource', Faye.Transport.EventSource);
2580
+
2581
+Faye.Transport.XHR = Faye.extend(Faye.Class(Faye.Transport, {
2582
+  encode: function(messages) {
2583
+    return Faye.toJSON(messages);
2584
+  },
2585
+
2586
+  request: function(messages) {
2587
+    var href = this.endpoint.href,
2588
+        xhr  = Faye.ENV.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(),
2589
+        self = this;
2590
+
2591
+    xhr.open('POST', href, true);
2592
+    xhr.setRequestHeader('Content-Type', 'application/json');
2593
+    xhr.setRequestHeader('Pragma', 'no-cache');
2594
+    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2595
+
2596
+    var headers = this._dispatcher.headers;
2597
+    for (var key in headers) {
2598
+      if (!headers.hasOwnProperty(key)) continue;
2599
+      xhr.setRequestHeader(key, headers[key]);
2600
+    }
2601
+
2602
+    var abort = function() { xhr.abort() };
2603
+    if (Faye.ENV.onbeforeunload !== undefined) Faye.Event.on(Faye.ENV, 'beforeunload', abort);
2604
+
2605
+    xhr.onreadystatechange = function() {
2606
+      if (!xhr || xhr.readyState !== 4) return;
2607
+
2608
+      var replies    = null,
2609
+          status     = xhr.status,
2610
+          text       = xhr.responseText,
2611
+          successful = (status >= 200 && status < 300) || status === 304 || status === 1223;
2612
+
2613
+      if (Faye.ENV.onbeforeunload !== undefined) Faye.Event.detach(Faye.ENV, 'beforeunload', abort);
2614
+      xhr.onreadystatechange = function() {};
2615
+      xhr = null;
2616
+
2617
+      if (!successful) return self._handleError(messages);
2618
+
2619
+      try {
2620
+        replies = JSON.parse(text);
2621
+      } catch (e) {}
2622
+
2623
+      if (replies)
2624
+        self._receive(replies);
2625
+      else
2626
+        self._handleError(messages);
2627
+    };
2628
+
2629
+    xhr.send(this.encode(messages));
2630
+    return xhr;
2631
+  }
2632
+}), {
2633
+  isUsable: function(dispatcher, endpoint, callback, context) {
2634
+    callback.call(context, Faye.URI.isSameOrigin(endpoint));
2635
+  }
2636
+});
2637
+
2638
+Faye.Transport.register('long-polling', Faye.Transport.XHR);
2639
+
2640
+Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, {
2641
+  encode: function(messages) {
2642
+    return 'message=' + encodeURIComponent(Faye.toJSON(messages));
2643
+  },
2644
+
2645
+  request: function(messages) {
2646
+    var xhrClass = Faye.ENV.XDomainRequest ? XDomainRequest : XMLHttpRequest,
2647
+        xhr      = new xhrClass(),
2648
+        headers  = this._dispatcher.headers,
2649
+        self     = this,
2650
+        key;
2651
+
2652
+    xhr.open('POST', Faye.URI.stringify(this.endpoint), true);
2653
+
2654
+    if (xhr.setRequestHeader) {
2655
+      xhr.setRequestHeader('Pragma', 'no-cache');
2656
+      for (key in headers) {
2657
+        if (!headers.hasOwnProperty(key)) continue;
2658
+        xhr.setRequestHeader(key, headers[key]);
2659
+      }
2660
+    }
2661
+
2662
+    var cleanUp = function() {
2663
+      if (!xhr) return false;
2664
+      xhr.onload = xhr.onerror = xhr.ontimeout = xhr.onprogress = null;
2665
+      xhr = null;
2666
+    };
2667
+
2668
+    xhr.onload = function() {
2669
+      var replies = null;
2670
+      try {
2671
+        replies = JSON.parse(xhr.responseText);
2672
+      } catch (e) {}
2673
+
2674
+      cleanUp();
2675
+
2676
+      if (replies)
2677
+        self._receive(replies);
2678
+      else
2679
+        self._handleError(messages);
2680
+    };
2681
+
2682
+    xhr.onerror = xhr.ontimeout = function() {
2683
+      cleanUp();
2684
+      self._handleError(messages);
2685
+    };
2686
+
2687
+    xhr.onprogress = function() {};
2688
+    xhr.send(this.encode(messages));
2689
+    return xhr;
2690
+  }
2691
+}), {
2692
+  isUsable: function(dispatcher, endpoint, callback, context) {
2693
+    if (Faye.URI.isSameOrigin(endpoint))
2694
+      return callback.call(context, false);
2695
+
2696
+    if (Faye.ENV.XDomainRequest)
2697
+      return callback.call(context, endpoint.protocol === Faye.ENV.location.protocol);
2698
+
2699
+    if (Faye.ENV.XMLHttpRequest) {
2700
+      var xhr = new Faye.ENV.XMLHttpRequest();
2701
+      return callback.call(context, xhr.withCredentials !== undefined);
2702
+    }
2703
+    return callback.call(context, false);
2704
+  }
2705
+});
2706
+
2707
+Faye.Transport.register('cross-origin-long-polling', Faye.Transport.CORS);
2708
+
2709
+Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, {
2710
+ encode: function(messages) {
2711
+    var url = Faye.copyObject(this.endpoint);
2712
+    url.query.message = Faye.toJSON(messages);
2713
+    url.query.jsonp   = '__jsonp' + Faye.Transport.JSONP._cbCount + '__';
2714
+    return Faye.URI.stringify(url);
2715
+  },
2716
+
2717
+  request: function(messages) {
2718
+    var head         = document.getElementsByTagName('head')[0],
2719
+        script       = document.createElement('script'),
2720
+        callbackName = Faye.Transport.JSONP.getCallbackName(),
2721
+        endpoint     = Faye.copyObject(this.endpoint),
2722
+        self         = this;
2723
+
2724
+    endpoint.query.message = Faye.toJSON(messages);
2725
+    endpoint.query.jsonp   = callbackName;
2726
+
2727
+    var cleanup = function() {
2728
+      if (!Faye.ENV[callbackName]) return false;
2729
+      Faye.ENV[callbackName] = undefined;
2730
+      try { delete Faye.ENV[callbackName] } catch (e) {}
2731
+      script.parentNode.removeChild(script);
2732
+    };
2733
+
2734
+    Faye.ENV[callbackName] = function(replies) {
2735
+      cleanup();
2736
+      self._receive(replies);
2737
+    };
2738
+
2739
+    script.type = 'text/javascript';
2740
+    script.src  = Faye.URI.stringify(endpoint);
2741
+    head.appendChild(script);
2742
+
2743
+    script.onerror = function() {
2744
+      cleanup();
2745
+      self._handleError(messages);
2746
+    };
2747
+
2748
+    return {abort: cleanup};
2749
+  }
2750
+}), {
2751
+  _cbCount: 0,
2752
+
2753
+  getCallbackName: function() {
2754
+    this._cbCount += 1;
2755
+    return '__jsonp' + this._cbCount + '__';
2756
+  },
2757
+
2758
+  isUsable: function(dispatcher, endpoint, callback, context) {
2759
+    callback.call(context, true);
2760
+  }
2761
+});
2762
+
2763
+Faye.Transport.register('callback-polling', Faye.Transport.JSONP);
2764
+
2765
+})();

+ 2194 - 0
app/scripts/libs/faye.js

@@ -0,0 +1,2194 @@
1
+'use strict';
2
+
3
+var Faye = {
4
+  VERSION:          '0.8.9',
5
+
6
+  BAYEUX_VERSION:   '1.0',
7
+  ID_LENGTH:        160,
8
+  JSONP_CALLBACK:   'jsonpcallback',
9
+  CONNECTION_TYPES: ['long-polling', 'cross-origin-long-polling', 'callback-polling', 'websocket', 'eventsource', 'in-process'],
10
+
11
+  MANDATORY_CONNECTION_TYPES: ['long-polling', 'callback-polling', 'in-process'],
12
+
13
+  ENV: (typeof global === 'undefined') ? window : global,
14
+
15
+  extend: function(dest, source, overwrite) {
16
+    if (!source) return dest;
17
+    for (var key in source) {
18
+      if (!source.hasOwnProperty(key)) continue;
19
+      if (dest.hasOwnProperty(key) && overwrite === false) continue;
20
+      if (dest[key] !== source[key])
21
+        dest[key] = source[key];
22
+    }
23
+    return dest;
24
+  },
25
+
26
+  random: function(bitlength) {
27
+    bitlength = bitlength || this.ID_LENGTH;
28
+    if (bitlength > 32) {
29
+      var parts  = Math.ceil(bitlength / 32),
30
+          string = '';
31
+      while (parts--) string += this.random(32);
32
+      var chars = string.split(''), result = '';
33
+      while (chars.length > 0) result += chars.pop();
34
+      return result;
35
+    }
36
+    var limit   = Math.pow(2, bitlength) - 1,
37
+        maxSize = limit.toString(36).length,
38
+        string  = Math.floor(Math.random() * limit).toString(36);
39
+
40
+    while (string.length < maxSize) string = '0' + string;
41
+    return string;
42
+  },
43
+
44
+  clientIdFromMessages: function(messages) {
45
+    var first = [].concat(messages)[0];
46
+    return first && first.clientId;
47
+  },
48
+
49
+  copyObject: function(object) {
50
+    var clone, i, key;
51
+    if (object instanceof Array) {
52
+      clone = [];
53
+      i = object.length;
54
+      while (i--) clone[i] = Faye.copyObject(object[i]);
55
+      return clone;
56
+    } else if (typeof object === 'object') {
57
+      clone = (object === null) ? null : {};
58
+      for (key in object) clone[key] = Faye.copyObject(object[key]);
59
+      return clone;
60
+    } else {
61
+      return object;
62
+    }
63
+  },
64
+
65
+  commonElement: function(lista, listb) {
66
+    for (var i = 0, n = lista.length; i < n; i++) {
67
+      if (this.indexOf(listb, lista[i]) !== -1)
68
+        return lista[i];
69
+    }
70
+    return null;
71
+  },
72
+
73
+  indexOf: function(list, needle) {
74
+    if (list.indexOf) return list.indexOf(needle);
75
+
76
+    for (var i = 0, n = list.length; i < n; i++) {
77
+      if (list[i] === needle) return i;
78
+    }
79
+    return -1;
80
+  },
81
+
82
+  map: function(object, callback, context) {
83
+    if (object.map) return object.map(callback, context);
84
+    var result = [];
85
+
86
+    if (object instanceof Array) {
87
+      for (var i = 0, n = object.length; i < n; i++) {
88
+        result.push(callback.call(context || null, object[i], i));
89
+      }
90
+    } else {
91
+      for (var key in object) {
92
+        if (!object.hasOwnProperty(key)) continue;
93
+        result.push(callback.call(context || null, key, object[key]));
94
+      }
95
+    }
96
+    return result;
97
+  },
98
+
99
+  filter: function(array, callback, context) {
100
+    var result = [];
101
+    for (var i = 0, n = array.length; i < n; i++) {
102
+      if (callback.call(context || null, array[i], i))
103
+        result.push(array[i]);
104
+    }
105
+    return result;
106
+  },
107
+
108
+  asyncEach: function(list, iterator, callback, context) {
109
+    var n       = list.length,
110
+        i       = -1,
111
+        calls   = 0,
112
+        looping = false;
113
+
114
+    var iterate = function() {
115
+      calls -= 1;
116
+      i += 1;
117
+      if (i === n) return callback && callback.call(context);
118
+      iterator(list[i], resume);
119
+    };
120
+
121
+    var loop = function() {
122
+      if (looping) return;
123
+      looping = true;
124
+      while (calls > 0) iterate();
125
+      looping = false;
126
+    };
127
+
128
+    var resume = function() {
129
+      calls += 1;
130
+      loop();
131
+    };
132
+    resume();
133
+  },
134
+
135
+  // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/
136
+  toJSON: function(object) {
137
+    if (this.stringify)
138
+      return this.stringify(object, function(key, value) {
139
+        return (this[key] instanceof Array)
140
+            ? this[key]
141
+            : value;
142
+      });
143
+
144
+    return JSON.stringify(object);
145
+  },
146
+
147
+  logger: function(message) {
148
+    if (typeof console !== 'undefined') console.log(message);
149
+  },
150
+
151
+  timestamp: function() {
152
+    var date   = new Date(),
153
+        year   = date.getFullYear(),
154
+        month  = date.getMonth() + 1,
155
+        day    = date.getDate(),
156
+        hour   = date.getHours(),
157
+        minute = date.getMinutes(),
158
+        second = date.getSeconds();
159
+
160
+    var pad = function(n) {
161
+      return n < 10 ? '0' + n : String(n);
162
+    };
163
+
164
+    return pad(year) + '-' + pad(month) + '-' + pad(day) + ' ' +
165
+           pad(hour) + ':' + pad(minute) + ':' + pad(second);
166
+  }
167
+};
168
+
169
+if (typeof window !== 'undefined')
170
+  window.Faye = Faye;
171
+
172
+
173
+Faye.Class = function(parent, methods) {
174
+  if (typeof parent !== 'function') {
175
+    methods = parent;
176
+    parent  = Object;
177
+  }
178
+
179
+  var klass = function() {
180
+    if (!this.initialize) return this;
181
+    return this.initialize.apply(this, arguments) || this;
182
+  };
183
+
184
+  var bridge = function() {};
185
+  bridge.prototype = parent.prototype;
186
+
187
+  klass.prototype = new bridge();
188
+  Faye.extend(klass.prototype, methods);
189
+
190
+  return klass;
191
+};
192
+
193
+
194
+Faye.Namespace = Faye.Class({
195
+  initialize: function() {
196
+    this._used = {};
197
+  },
198
+
199
+  exists: function(id) {
200
+    return this._used.hasOwnProperty(id);
201
+  },
202
+
203
+  generate: function() {
204
+    var name = Faye.random();
205
+    while (this._used.hasOwnProperty(name))
206
+      name = Faye.random();
207
+    return this._used[name] = name;
208
+  },
209
+
210
+  release: function(id) {
211
+    delete this._used[id];
212
+  }
213
+});
214
+
215
+
216
+Faye.Error = Faye.Class({
217
+  initialize: function(code, params, message) {
218
+    this.code    = code;
219
+    this.params  = Array.prototype.slice.call(params);
220
+    this.message = message;
221
+  },
222
+
223
+  toString: function() {
224
+    return this.code + ':' +
225
+           this.params.join(',') + ':' +
226
+           this.message;
227
+  }
228
+});
229
+
230
+Faye.Error.parse = function(message) {
231
+  message = message || '';
232
+  if (!Faye.Grammar.ERROR.test(message)) return new this(null, [], message);
233
+
234
+  var parts   = message.split(':'),
235
+      code    = parseInt(parts[0]),
236
+      params  = parts[1].split(','),
237
+      message = parts[2];
238
+
239
+  return new this(code, params, message);
240
+};
241
+
242
+
243
+Faye.Error.versionMismatch = function() {
244
+  return new this(300, arguments, "Version mismatch").toString();
245
+};
246
+
247
+Faye.Error.conntypeMismatch = function() {
248
+  return new this(301, arguments, "Connection types not supported").toString();
249
+};
250
+
251
+Faye.Error.extMismatch = function() {
252
+  return new this(302, arguments, "Extension mismatch").toString();
253
+};
254
+
255
+Faye.Error.badRequest = function() {
256
+  return new this(400, arguments, "Bad request").toString();
257
+};
258
+
259
+Faye.Error.clientUnknown = function() {
260
+  return new this(401, arguments, "Unknown client").toString();
261
+};
262
+
263
+Faye.Error.parameterMissing = function() {
264
+  return new this(402, arguments, "Missing required parameter").toString();
265
+};
266
+
267
+Faye.Error.channelForbidden = function() {
268
+  return new this(403, arguments, "Forbidden channel").toString();
269
+};
270
+
271
+Faye.Error.channelUnknown = function() {
272
+  return new this(404, arguments, "Unknown channel").toString();
273
+};
274
+
275
+Faye.Error.channelInvalid = function() {
276
+  return new this(405, arguments, "Invalid channel").toString();
277
+};
278
+
279
+Faye.Error.extUnknown = function() {
280
+  return new this(406, arguments, "Unknown extension").toString();
281
+};
282
+
283
+Faye.Error.publishFailed = function() {
284
+  return new this(407, arguments, "Failed to publish").toString();
285
+};
286
+
287
+Faye.Error.serverError = function() {
288
+  return new this(500, arguments, "Internal server error").toString();
289
+};
290
+
291
+
292
+
293
+Faye.Deferrable = {
294
+  callback: function(callback, context) {
295
+    if (!callback) return;
296
+
297
+    if (this._deferredStatus === 'succeeded')
298
+      return callback.apply(context, this._deferredArgs);
299
+
300
+    this._callbacks = this._callbacks || [];
301
+    this._callbacks.push([callback, context]);
302
+  },
303
+
304
+  timeout: function(seconds, message) {
305
+    var _this = this;
306
+    var timer = Faye.ENV.setTimeout(function() {
307
+      _this.setDeferredStatus('failed', message);
308
+    }, seconds * 1000);
309
+    this._timer = timer;
310
+  },
311
+
312
+  errback: function(callback, context) {
313
+    if (!callback) return;
314
+
315
+    if (this._deferredStatus === 'failed')
316
+      return callback.apply(context, this._deferredArgs);
317
+
318
+    this._errbacks = this._errbacks || [];
319
+    this._errbacks.push([callback, context]);
320
+  },
321
+
322
+  setDeferredStatus: function() {
323
+    if (this._timer)
324
+      Faye.ENV.clearTimeout(this._timer);
325
+
326
+    var args   = Array.prototype.slice.call(arguments),
327
+        status = args.shift(),
328
+        callbacks;
329
+
330
+    this._deferredStatus = status;
331
+    this._deferredArgs = args;
332
+
333
+    if (status === 'succeeded')
334
+      callbacks = this._callbacks;
335
+    else if (status === 'failed')
336
+      callbacks = this._errbacks;
337
+
338
+    if (!callbacks) return;
339
+
340
+    var callback;
341
+    while (callback = callbacks.shift())
342
+      callback[0].apply(callback[1], this._deferredArgs);
343
+  }
344
+};
345
+
346
+
347
+Faye.Publisher = {
348
+  countListeners: function(eventType) {
349
+    if (!this._subscribers || !this._subscribers[eventType]) return 0;
350
+    return this._subscribers[eventType].length;
351
+  },
352
+
353
+  bind: function(eventType, listener, context) {
354
+    this._subscribers = this._subscribers || {};
355
+    var list = this._subscribers[eventType] = this._subscribers[eventType] || [];
356
+    list.push([listener, context]);
357
+  },
358
+
359
+  unbind: function(eventType, listener, context) {
360
+    if (!this._subscribers || !this._subscribers[eventType]) return;
361
+
362
+    if (!listener) {
363
+      delete this._subscribers[eventType];
364
+      return;
365
+    }
366
+    var list = this._subscribers[eventType],
367
+        i    = list.length;
368
+
369
+    while (i--) {
370
+      if (listener !== list[i][0]) continue;
371
+      if (context && list[i][1] !== context) continue;
372
+      list.splice(i,1);
373
+    }
374
+  },
375
+
376
+  trigger: function() {
377
+    var args = Array.prototype.slice.call(arguments),
378
+        eventType = args.shift();
379
+
380
+    if (!this._subscribers || !this._subscribers[eventType]) return;
381
+
382
+    var listeners = this._subscribers[eventType].slice(),
383
+        listener;
384
+
385
+    for (var i = 0, n = listeners.length; i < n; i++) {
386
+      listener = listeners[i];
387
+      listener[0].apply(listener[1], args);
388
+    }
389
+  }
390
+};
391
+
392
+
393
+Faye.Timeouts = {
394
+  addTimeout: function(name, delay, callback, context) {
395
+    this._timeouts = this._timeouts || {};
396
+    if (this._timeouts.hasOwnProperty(name)) return;
397
+    var self = this;
398
+    this._timeouts[name] = Faye.ENV.setTimeout(function() {
399
+      delete self._timeouts[name];
400
+      callback.call(context);
401
+    }, 1000 * delay);
402
+  },
403
+
404
+  removeTimeout: function(name) {
405
+    this._timeouts = this._timeouts || {};
406
+    var timeout = this._timeouts[name];
407
+    if (!timeout) return;
408
+    clearTimeout(timeout);
409
+    delete this._timeouts[name];
410
+  }
411
+};
412
+
413
+
414
+Faye.Logging = {
415
+  LOG_LEVELS: {
416
+    error:  3,
417
+    warn:   2,
418
+    info:   1,
419
+    debug:  0
420
+  },
421
+
422
+  logLevel: 'error',
423
+
424
+  log: function(messageArgs, level) {
425
+    if (!Faye.logger) return;
426
+
427
+    var levels = Faye.Logging.LOG_LEVELS;
428
+    if (levels[Faye.Logging.logLevel] > levels[level]) return;
429
+
430
+    var messageArgs = Array.prototype.slice.apply(messageArgs),
431
+        banner = ' [' + level.toUpperCase() + '] [Faye',
432
+        klass  = this.className,
433
+
434
+        message = messageArgs.shift().replace(/\?/g, function() {
435
+          try {
436
+            return Faye.toJSON(messageArgs.shift());
437
+          } catch (e) {
438
+            return '[Object]';
439
+          }
440
+        });
441
+
442
+    for (var key in Faye) {
443
+      if (klass) continue;
444
+      if (typeof Faye[key] !== 'function') continue;
445
+      if (this instanceof Faye[key]) klass = key;
446
+    }
447
+    if (klass) banner += '.' + klass;
448
+    banner += '] ';
449
+
450
+    Faye.logger(Faye.timestamp() + banner + message);
451
+  }
452
+};
453
+
454
+(function() {
455
+  for (var key in Faye.Logging.LOG_LEVELS)
456
+    (function(level, value) {
457
+      Faye.Logging[level] = function() {
458
+        this.log(arguments, level);
459
+      };
460
+    })(key, Faye.Logging.LOG_LEVELS[key]);
461
+})();
462
+
463
+
464
+Faye.Grammar = {
465
+
466
+  LOWALPHA:     /^[a-z]$/,
467
+
468
+  UPALPHA:     /^[A-Z]$/,
469
+
470
+  ALPHA:     /^([a-z]|[A-Z])$/,
471
+
472
+  DIGIT:     /^[0-9]$/,
473
+
474
+  ALPHANUM:     /^(([a-z]|[A-Z])|[0-9])$/,
475
+
476
+  MARK:     /^(\-|\_|\!|\~|\(|\)|\$|\@)$/,
477
+
478
+  STRING:     /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,
479
+
480
+  TOKEN:     /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,
481
+
482
+  INTEGER:     /^([0-9])+$/,
483
+
484
+  CHANNEL_SEGMENT:     /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,
485
+
486
+  CHANNEL_SEGMENTS:     /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,
487
+
488
+  CHANNEL_NAME:     /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,
489
+
490
+  WILD_CARD:     /^\*{1,2}$/,
491
+
492
+  CHANNEL_PATTERN:     /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,
493
+
494
+  VERSION_ELEMENT:     /^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/,
495
+
496
+  VERSION:     /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/,
497
+
498
+  CLIENT_ID:     /^((([a-z]|[A-Z])|[0-9]))+$/,
499
+
500
+  ID:     /^((([a-z]|[A-Z])|[0-9]))+$/,
501
+
502
+  ERROR_MESSAGE:     /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,
503
+
504
+  ERROR_ARGS:     /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/,
505
+
506
+  ERROR_CODE:     /^[0-9][0-9][0-9]$/,
507
+
508
+  ERROR:     /^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/
509
+
510
+};
511
+
512
+
513
+Faye.Extensible = {
514
+  addExtension: function(extension) {
515
+    this._extensions = this._extensions || [];
516
+    this._extensions.push(extension);
517
+    if (extension.added) extension.added(this);
518
+  },
519
+
520
+  removeExtension: function(extension) {
521
+    if (!this._extensions) return;
522
+    var i = this._extensions.length;
523
+    while (i--) {
524
+      if (this._extensions[i] !== extension) continue;
525
+      this._extensions.splice(i,1);
526
+      if (extension.removed) extension.removed(this);
527
+    }
528
+  },
529
+
530
+  pipeThroughExtensions: function(stage, message, callback, context) {
531
+    this.debug('Passing through ? extensions: ?', stage, message);
532
+
533
+    if (!this._extensions) return callback.call(context, message);
534
+    var extensions = this._extensions.slice();
535
+
536
+    var pipe = function(message) {
537
+      if (!message) return callback.call(context, message);
538
+
539
+      var extension = extensions.shift();
540
+      if (!extension) return callback.call(context, message);
541
+
542
+      if (extension[stage]) extension[stage](message, pipe);
543
+      else pipe(message);
544
+    };
545
+    pipe(message);
546
+  }
547
+};
548
+
549
+Faye.extend(Faye.Extensible, Faye.Logging);
550
+
551
+Faye.Channel = Faye.Class({
552
+  initialize: function(name) {
553
+    this.id = this.name = name;
554
+  },
555
+
556
+  push: function(message) {
557
+    this.trigger('message', message);
558
+  },
559
+
560
+  isUnused: function() {
561
+    return this.countListeners('message') === 0;
562
+  }
563
+});
564
+
565
+Faye.extend(Faye.Channel.prototype, Faye.Publisher);
566
+
567
+Faye.extend(Faye.Channel, {
568
+  HANDSHAKE:    '/meta/handshake',
569
+  CONNECT:      '/meta/connect',
570
+  SUBSCRIBE:    '/meta/subscribe',
571
+  UNSUBSCRIBE:  '/meta/unsubscribe',
572
+  DISCONNECT:   '/meta/disconnect',
573
+
574
+  META:         'meta',
575
+  SERVICE:      'service',
576
+
577
+  expand: function(name) {
578
+    var segments = this.parse(name),
579
+        channels = ['/**', name];
580
+
581
+    var copy = segments.slice();
582
+    copy[copy.length - 1] = '*';
583
+    channels.push(this.unparse(copy));
584
+
585
+    for (var i = 1, n = segments.length; i < n; i++) {
586
+      copy = segments.slice(0, i);
587
+      copy.push('**');
588
+      channels.push(this.unparse(copy));
589
+    }
590
+
591
+    return channels;
592
+  },
593
+
594
+  isValid: function(name) {
595
+    return Faye.Grammar.CHANNEL_NAME.test(name) ||
596
+           Faye.Grammar.CHANNEL_PATTERN.test(name);
597
+  },
598
+
599
+  parse: function(name) {
600
+    if (!this.isValid(name)) return null;
601
+    return name.split('/').slice(1);
602
+  },
603
+
604
+  unparse: function(segments) {
605
+    return '/' + segments.join('/');
606
+  },
607
+
608
+  isMeta: function(name) {
609
+    var segments = this.parse(name);
610
+    return segments ? (segments[0] === this.META) : null;
611
+  },
612
+
613
+  isService: function(name) {
614
+    var segments = this.parse(name);
615
+    return segments ? (segments[0] === this.SERVICE) : null;
616
+  },
617
+
618
+  isSubscribable: function(name) {
619
+    if (!this.isValid(name)) return null;
620
+    return !this.isMeta(name) && !this.isService(name);
621
+  },
622
+
623
+  Set: Faye.Class({
624
+    initialize: function() {
625
+      this._channels = {};
626
+    },
627
+
628
+    getKeys: function() {
629
+      var keys = [];
630
+      for (var key in this._channels) keys.push(key);
631
+      return keys;
632
+    },
633
+
634
+    remove: function(name) {
635
+      delete this._channels[name];
636
+    },
637
+
638
+    hasSubscription: function(name) {
639
+      return this._channels.hasOwnProperty(name);
640
+    },
641
+
642
+    subscribe: function(names, callback, context) {
643
+      if (!callback) return;
644
+      var name;
645
+      for (var i = 0, n = names.length; i < n; i++) {
646
+        name = names[i];
647
+        var channel = this._channels[name] = this._channels[name] || new Faye.Channel(name);
648
+        channel.bind('message', callback, context);
649
+      }
650
+    },
651
+
652
+    unsubscribe: function(name, callback, context) {
653
+      var channel = this._channels[name];
654
+      if (!channel) return false;
655
+      channel.unbind('message', callback, context);
656
+
657
+      if (channel.isUnused()) {
658
+        this.remove(name);
659
+        return true;
660
+      } else {
661
+        return false;
662
+      }
663
+    },
664
+
665
+    distributeMessage: function(message) {
666
+      var channels = Faye.Channel.expand(message.channel);
667
+
668
+      for (var i = 0, n = channels.length; i < n; i++) {
669
+        var channel = this._channels[channels[i]];
670
+        if (channel) channel.trigger('message', message.data);
671
+      }
672
+    }
673
+  })
674
+});
675
+
676
+
677
+Faye.Publication = Faye.Class(Faye.Deferrable);
678
+
679
+
680
+Faye.Subscription = Faye.Class({
681
+  initialize: function(client, channels, callback, context) {
682
+    this._client    = client;
683
+    this._channels  = channels;
684
+    this._callback  = callback;
685
+    this._context     = context;
686
+    this._cancelled = false;
687
+  },
688
+
689
+  cancel: function() {
690
+    if (this._cancelled) return;
691
+    this._client.unsubscribe(this._channels, this._callback, this._context);
692
+    this._cancelled = true;
693
+  },
694
+
695
+  unsubscribe: function() {
696
+    this.cancel();
697
+  }
698
+});
699
+
700
+Faye.extend(Faye.Subscription.prototype, Faye.Deferrable);
701
+
702
+
703
+Faye.Client = Faye.Class({
704
+  UNCONNECTED:          1,
705
+  CONNECTING:           2,
706
+  CONNECTED:            3,
707
+  DISCONNECTED:         4,
708
+
709
+  HANDSHAKE:            'handshake',
710
+  RETRY:                'retry',
711
+  NONE:                 'none',
712
+
713
+  CONNECTION_TIMEOUT:   60.0,
714
+  DEFAULT_RETRY:        5.0,
715
+
716
+  DEFAULT_ENDPOINT:     '/bayeux',
717
+  INTERVAL:             0.0,
718
+
719
+  initialize: function(endpoint, options) {
720
+    this.info('New client created for ?', endpoint);
721
+
722
+    this._options   = options || {};
723
+    this.endpoint   = endpoint || this.DEFAULT_ENDPOINT;
724
+    this.endpoints  = this._options.endpoints || {};
725
+    this.transports = {};
726
+    this._cookies   = Faye.CookieJar && new Faye.CookieJar();
727
+    this._headers   = {};
728
+    this._disabled  = [];
729
+    this.retry      = this._options.retry || this.DEFAULT_RETRY;
730
+
731
+    this._state     = this.UNCONNECTED;
732
+    this._channels  = new Faye.Channel.Set();
733
+    this._messageId = 0;
734
+
735
+    this._responseCallbacks = {};
736
+
737
+    this._advice = {
738
+      reconnect: this.RETRY,
739
+      interval:  1000 * (this._options.interval || this.INTERVAL),
740
+      timeout:   1000 * (this._options.timeout  || this.CONNECTION_TIMEOUT)
741
+    };
742
+
743
+    if (Faye.Event)
744
+      Faye.Event.on(Faye.ENV, 'beforeunload', function() {
745
+        if (Faye.indexOf(this._disabled, 'autodisconnect') < 0)
746
+          this.disconnect();
747
+      }, this);
748
+  },
749
+
750
+  disable: function(feature) {
751
+    this._disabled.push(feature);
752
+  },
753
+
754
+  setHeader: function(name, value) {
755
+    this._headers[name] = value;
756
+  },
757
+
758
+  getClientId: function() {
759
+    return this._clientId;
760
+  },
761
+
762
+  getState: function() {
763
+    switch (this._state) {
764
+      case this.UNCONNECTED:  return 'UNCONNECTED';
765
+      case this.CONNECTING:   return 'CONNECTING';
766
+      case this.CONNECTED:    return 'CONNECTED';
767
+      case this.DISCONNECTED: return 'DISCONNECTED';
768
+    }
769
+  },
770
+
771
+  // Request
772
+  // MUST include:  * channel
773
+  //                * version
774
+  //                * supportedConnectionTypes
775
+  // MAY include:   * minimumVersion
776
+  //                * ext
777
+  //                * id
778
+  //
779
+  // Success Response                             Failed Response
780
+  // MUST include:  * channel                     MUST include:  * channel
781
+  //                * version                                    * successful
782
+  //                * supportedConnectionTypes                   * error
783
+  //                * clientId                    MAY include:   * supportedConnectionTypes
784
+  //                * successful                                 * advice
785
+  // MAY include:   * minimumVersion                             * version
786
+  //                * advice                                     * minimumVersion
787
+  //                * ext                                        * ext
788
+  //                * id                                         * id
789
+  //                * authSuccessful
790
+  handshake: function(callback, context) {
791
+    if (this._advice.reconnect === this.NONE) return;
792
+    if (this._state !== this.UNCONNECTED) return;
793
+
794
+    this._state = this.CONNECTING;
795
+    var self = this;
796
+
797
+    this.info('Initiating handshake with ?', this.endpoint);
798
+    this._selectTransport(Faye.MANDATORY_CONNECTION_TYPES);
799
+
800
+    this._send({
801
+      channel:      Faye.Channel.HANDSHAKE,
802
+      version:      Faye.BAYEUX_VERSION,
803
+      supportedConnectionTypes: [this._transport.connectionType]
804
+
805
+    }, function(response) {
806
+
807
+      if (response.successful) {
808
+        this._state     = this.CONNECTED;
809
+        this._clientId  = response.clientId;
810
+
811
+        this._selectTransport(response.supportedConnectionTypes);
812
+
813
+        this.info('Handshake successful: ?', this._clientId);
814
+
815
+        this.subscribe(this._channels.getKeys(), true);
816
+        if (callback) callback.call(context);
817
+
818
+      } else {
819
+        this.info('Handshake unsuccessful');
820
+        Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._advice.interval);
821
+        this._state = this.UNCONNECTED;
822
+      }
823
+    }, this);
824
+  },
825
+
826
+  // Request                              Response
827
+  // MUST include:  * channel             MUST include:  * channel
828
+  //                * clientId                           * successful
829
+  //                * connectionType                     * clientId
830
+  // MAY include:   * ext                 MAY include:   * error
831
+  //                * id                                 * advice
832
+  //                                                     * ext
833
+  //                                                     * id
834
+  //                                                     * timestamp
835
+  connect: function(callback, context) {
836
+    if (this._advice.reconnect === this.NONE) return;
837
+    if (this._state === this.DISCONNECTED) return;
838
+
839
+    if (this._state === this.UNCONNECTED)
840
+      return this.handshake(function() { this.connect(callback, context) }, this);
841
+
842
+    this.callback(callback, context);
843
+    if (this._state !== this.CONNECTED) return;
844
+
845
+    this.info('Calling deferred actions for ?', this._clientId);
846
+    this.setDeferredStatus('succeeded');
847
+    this.setDeferredStatus('deferred');
848
+
849
+    if (this._connectRequest) return;
850
+    this._connectRequest = true;
851
+
852
+    this.info('Initiating connection for ?', this._clientId);
853
+
854
+    this._send({
855
+      channel:        Faye.Channel.CONNECT,
856
+      clientId:       this._clientId,
857
+      connectionType: this._transport.connectionType
858
+
859
+    }, this._cycleConnection, this);
860
+  },
861
+
862
+  // Request                              Response
863
+  // MUST include:  * channel             MUST include:  * channel
864
+  //                * clientId                           * successful
865
+  // MAY include:   * ext                                * clientId
866
+  //                * id                  MAY include:   * error
867
+  //                                                     * ext
868
+  //                                                     * id
869
+  disconnect: function() {
870
+    if (this._state !== this.CONNECTED) return;
871
+    this._state = this.DISCONNECTED;
872
+
873
+    this.info('Disconnecting ?', this._clientId);
874
+
875
+    this._send({
876
+      channel:    Faye.Channel.DISCONNECT,
877
+      clientId:   this._clientId
878
+
879
+    }, function(response) {
880
+      if (response.successful) this._transport.close();
881
+    }, this);
882
+
883
+    this.info('Clearing channel listeners for ?', this._clientId);
884
+    this._channels = new Faye.Channel.Set();
885
+  },
886
+
887
+  // Request                              Response
888
+  // MUST include:  * channel             MUST include:  * channel
889
+  //                * clientId                           * successful
890
+  //                * subscription                       * clientId
891
+  // MAY include:   * ext                                * subscription
892
+  //                * id                  MAY include:   * error
893
+  //                                                     * advice
894
+  //                                                     * ext
895
+  //                                                     * id
896
+  //                                                     * timestamp
897
+  subscribe: function(channel, callback, context) {
898
+    if (channel instanceof Array)
899
+      return Faye.map(channel, function(c) {
900
+        return this.subscribe(c, callback, context);
901
+      }, this);
902
+
903
+    var subscription = new Faye.Subscription(this, channel, callback, context),
904
+        force        = (callback === true),
905
+        hasSubscribe = this._channels.hasSubscription(channel);
906
+
907
+    if (hasSubscribe && !force) {
908
+      this._channels.subscribe([channel], callback, context);
909
+      subscription.setDeferredStatus('succeeded');
910
+      return subscription;
911
+    }
912
+
913
+    this.connect(function() {
914
+      this.info('Client ? attempting to subscribe to ?', this._clientId, channel);
915
+      if (!force) this._channels.subscribe([channel], callback, context);
916
+
917
+      this._send({
918
+        channel:      Faye.Channel.SUBSCRIBE,
919
+        clientId:     this._clientId,
920
+        subscription: channel
921
+
922
+      }, function(response) {
923
+        if (!response.successful) {
924
+          subscription.setDeferredStatus('failed', Faye.Error.parse(response.error));
925
+          return this._channels.unsubscribe(channel, callback, context);
926
+        }
927
+
928
+        var channels = [].concat(response.subscription);
929
+        this.info('Subscription acknowledged for ? to ?', this._clientId, channels);
930
+        subscription.setDeferredStatus('succeeded');
931
+      }, this);
932
+    }, this);
933
+
934
+    return subscription;
935
+  },
936
+
937
+  // Request                              Response
938
+  // MUST include:  * channel             MUST include:  * channel
939
+  //                * clientId                           * successful
940
+  //                * subscription                       * clientId
941
+  // MAY include:   * ext                                * subscription
942
+  //                * id                  MAY include:   * error
943
+  //                                                     * advice
944
+  //                                                     * ext
945
+  //                                                     * id
946
+  //                                                     * timestamp
947
+  unsubscribe: function(channel, callback, context) {
948
+    if (channel instanceof Array)
949
+      return Faye.map(channel, function(c) {
950
+        return this.unsubscribe(c, callback, context);
951
+      }, this);
952
+
953
+    var dead = this._channels.unsubscribe(channel, callback, context);
954
+    if (!dead) return;
955
+
956
+    this.connect(function() {
957
+      this.info('Client ? attempting to unsubscribe from ?', this._clientId, channel);
958
+
959
+      this._send({
960
+        channel:      Faye.Channel.UNSUBSCRIBE,
961
+        clientId:     this._clientId,
962
+        subscription: channel
963
+
964
+      }, function(response) {
965
+        if (!response.successful) return;
966
+
967
+        var channels = [].concat(response.subscription);
968
+        this.info('Unsubscription acknowledged for ? from ?', this._clientId, channels);
969
+      }, this);
970
+    }, this);
971
+  },
972
+
973
+  // Request                              Response
974
+  // MUST include:  * channel             MUST include:  * channel
975
+  //                * data                               * successful
976
+  // MAY include:   * clientId            MAY include:   * id
977
+  //                * id                                 * error
978
+  //                * ext                                * ext
979
+  publish: function(channel, data) {
980
+    var publication = new Faye.Publication();
981
+
982
+    this.connect(function() {
983
+      this.info('Client ? queueing published message to ?: ?', this._clientId, channel, data);
984
+
985
+      this._send({
986
+        channel:      channel,
987
+        data:         data,
988
+        clientId:     this._clientId
989
+      }, function(response) {
990
+        if (response.successful)
991
+          publication.setDeferredStatus('succeeded');
992
+        else
993
+          publication.setDeferredStatus('failed', Faye.Error.parse(response.error));
994
+      }, this);
995
+    }, this);
996
+
997
+    return publication;
998
+  },
999
+
1000
+  receiveMessage: function(message) {
1001
+    this.pipeThroughExtensions('incoming', message, function(message) {
1002
+      if (!message) return;
1003
+
1004
+      if (message.advice) this._handleAdvice(message.advice);
1005
+      this._deliverMessage(message);
1006
+
1007
+      if (message.successful === undefined) return;
1008
+
1009
+      var callback = this._responseCallbacks[message.id];
1010
+      if (!callback) return;
1011
+
1012
+      delete this._responseCallbacks[message.id];
1013
+      callback[0].call(callback[1], message);
1014
+    }, this);
1015
+  },
1016
+
1017
+  _selectTransport: function(transportTypes) {
1018
+    Faye.Transport.get(this, transportTypes, this._disabled, function(transport) {
1019
+      this.debug('Selected ? transport for ?', transport.connectionType, transport.endpoint);
1020
+
1021
+      if (transport === this._transport) return;
1022
+      if (this._transport) this._transport.close();
1023
+
1024
+      this._transport = transport;
1025
+      this._transport.cookies = this._cookies;
1026
+      this._transport.headers = this._headers;
1027
+
1028
+      transport.bind('down', function() {
1029
+        if (this._transportUp !== undefined && !this._transportUp) return;
1030
+        this._transportUp = false;
1031
+        this.trigger('transport:down');
1032
+      }, this);
1033
+
1034
+      transport.bind('up', function() {
1035
+        if (this._transportUp !== undefined && this._transportUp) return;
1036
+        this._transportUp = true;
1037
+        this.trigger('transport:up');
1038
+      }, this);
1039
+    }, this);
1040
+  },
1041
+
1042
+  _send: function(message, callback, context) {
1043
+    message.id = this._generateMessageId();
1044
+    if (callback) this._responseCallbacks[message.id] = [callback, context];
1045
+
1046
+    this.pipeThroughExtensions('outgoing', message, function(message) {
1047
+      if (!message) return;
1048
+      this._transport.send(message, this._advice.timeout / 1000);
1049
+    }, this);
1050
+  },
1051
+
1052
+  _generateMessageId: function() {
1053
+    this._messageId += 1;
1054
+    if (this._messageId >= Math.pow(2,32)) this._messageId = 0;
1055
+    return this._messageId.toString(36);
1056
+  },
1057
+
1058
+  _handleAdvice: function(advice) {
1059
+    Faye.extend(this._advice, advice);
1060
+
1061
+    if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) {
1062
+      this._state    = this.UNCONNECTED;
1063
+      this._clientId = null;
1064
+      this._cycleConnection();
1065
+    }
1066
+  },
1067
+
1068
+  _deliverMessage: function(message) {
1069
+    if (!message.channel || message.data === undefined) return;
1070
+    this.info('Client ? calling listeners for ? with ?', this._clientId, message.channel, message.data);
1071
+    this._channels.distributeMessage(message);
1072
+  },
1073
+
1074
+  _teardownConnection: function() {
1075
+    if (!this._connectRequest) return;
1076
+    this._connectRequest = null;
1077
+    this.info('Closed connection for ?', this._clientId);
1078
+  },
1079
+
1080
+  _cycleConnection: function() {
1081
+    this._teardownConnection();
1082
+    var self = this;
1083
+    Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval);
1084
+  }
1085
+});
1086
+
1087
+Faye.extend(Faye.Client.prototype, Faye.Deferrable);
1088
+Faye.extend(Faye.Client.prototype, Faye.Publisher);
1089
+Faye.extend(Faye.Client.prototype, Faye.Logging);
1090
+Faye.extend(Faye.Client.prototype, Faye.Extensible);
1091
+
1092
+
1093
+Faye.Transport = Faye.extend(Faye.Class({
1094
+  MAX_DELAY: 0.0,
1095
+  batching:  true,
1096
+
1097
+  initialize: function(client, endpoint) {
1098
+    this._client  = client;
1099
+    this.endpoint = endpoint;
1100
+    this._outbox  = [];
1101
+  },
1102
+
1103
+  close: function() {},
1104
+
1105
+  send: function(message, timeout) {
1106
+    this.debug('Client ? sending message to ?: ?',
1107
+               this._client._clientId, this.endpoint, message);
1108
+
1109
+    if (!this.batching) return this.request([message], timeout);
1110
+
1111
+    this._outbox.push(message);
1112
+    this._timeout = timeout;
1113
+
1114
+    if (message.channel === Faye.Channel.HANDSHAKE)
1115
+      return this.addTimeout('publish', 0.01, this.flush, this);
1116
+
1117
+    if (message.channel === Faye.Channel.CONNECT)
1118
+      this._connectMessage = message;
1119
+
1120
+    if (this.shouldFlush && this.shouldFlush(this._outbox))
1121
+      return this.flush();
1122
+
1123
+    this.addTimeout('publish', this.MAX_DELAY, this.flush, this);
1124
+  },
1125
+
1126
+  flush: function() {
1127
+    this.removeTimeout('publish');
1128
+
1129
+    if (this._outbox.length > 1 && this._connectMessage)
1130
+      this._connectMessage.advice = {timeout: 0};
1131
+
1132
+    this.request(this._outbox, this._timeout);
1133
+
1134
+    this._connectMessage = null;
1135
+    this._outbox = [];
1136
+  },
1137
+
1138
+  receive: function(responses) {
1139
+    this.debug('Client ? received from ?: ?',
1140
+               this._client._clientId, this.endpoint, responses);
1141
+
1142
+    for (var i = 0, n = responses.length; i < n; i++) {
1143
+      this._client.receiveMessage(responses[i]);
1144
+    }
1145
+  },
1146
+
1147
+  retry: function(message, timeout) {
1148
+    var called = false,
1149
+        retry  = this._client.retry * 1000,
1150
+        self   = this;
1151
+
1152
+    return function() {
1153
+      if (called) return;
1154
+      called = true;
1155
+      Faye.ENV.setTimeout(function() { self.request(message, timeout) }, retry);
1156
+    };
1157
+  }
1158
+
1159
+}), {
1160
+  MAX_URL_LENGTH: 2048,
1161
+
1162
+  get: function(client, allowed, disabled, callback, context) {
1163
+    var endpoint = client.endpoint;
1164
+
1165
+    Faye.asyncEach(this._transports, function(pair, resume) {
1166
+      var connType     = pair[0], klass = pair[1],
1167
+          connEndpoint = client.endpoints[connType] || endpoint;
1168
+
1169
+      if (Faye.indexOf(disabled, connType) >= 0)
1170
+        return resume();
1171
+
1172
+      if (Faye.indexOf(allowed, connType) < 0) {
1173
+        klass.isUsable(client, connEndpoint, function() {});
1174
+        return resume();
1175
+      }
1176
+
1177
+      klass.isUsable(client, connEndpoint, function(isUsable) {
1178
+        if (!isUsable) return resume();
1179
+        var transport = klass.hasOwnProperty('create') ? klass.create(client, connEndpoint) : new klass(client, connEndpoint);
1180
+        callback.call(context, transport);
1181
+      });
1182
+    }, function() {
1183
+      throw new Error('Could not find a usable connection type for ' + endpoint);
1184
+    });
1185
+  },
1186
+
1187
+  register: function(type, klass) {
1188
+    this._transports.push([type, klass]);
1189
+    klass.prototype.connectionType = type;
1190
+  },
1191
+
1192
+  _transports: []
1193
+});
1194
+
1195
+Faye.extend(Faye.Transport.prototype, Faye.Logging);
1196
+Faye.extend(Faye.Transport.prototype, Faye.Publisher);
1197
+Faye.extend(Faye.Transport.prototype, Faye.Timeouts);
1198
+
1199
+
1200
+Faye.Event = {
1201
+  _registry: [],
1202
+
1203
+  on: function(element, eventName, callback, context) {
1204
+    var wrapped = function() { callback.call(context) };
1205
+
1206
+    if (element.addEventListener)
1207
+      element.addEventListener(eventName, wrapped, false);
1208
+    else
1209
+      element.attachEvent('on' + eventName, wrapped);
1210
+
1211
+    this._registry.push({
1212
+      _element:   element,
1213
+      _type:      eventName,
1214
+      _callback:  callback,
1215
+      _context:     context,
1216
+      _handler:   wrapped
1217
+    });
1218
+  },
1219
+
1220
+  detach: function(element, eventName, callback, context) {
1221
+    var i = this._registry.length, register;
1222
+    while (i--) {
1223
+      register = this._registry[i];
1224
+
1225
+      if ((element    && element    !== register._element)   ||
1226
+          (eventName  && eventName  !== register._type)      ||
1227
+          (callback   && callback   !== register._callback)  ||
1228
+          (context      && context      !== register._context))
1229
+        continue;
1230
+
1231
+      if (register._element.removeEventListener)
1232
+        register._element.removeEventListener(register._type, register._handler, false);
1233
+      else
1234
+        register._element.detachEvent('on' + register._type, register._handler);
1235
+
1236
+      this._registry.splice(i,1);
1237
+      register = null;
1238
+    }
1239
+  }
1240
+};
1241
+
1242
+Faye.Event.on(Faye.ENV, 'unload', Faye.Event.detach, Faye.Event);
1243
+
1244
+
1245
+Faye.URI = Faye.extend(Faye.Class({
1246
+  queryString: function() {
1247
+    var pairs = [];
1248
+    for (var key in this.params) {
1249
+      if (!this.params.hasOwnProperty(key)) continue;
1250
+      pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.params[key]));
1251
+    }
1252
+    return pairs.join('&');
1253
+  },
1254
+
1255
+  isSameOrigin: function() {
1256
+    var host = Faye.URI.parse(Faye.ENV.location.href, false);
1257
+
1258
+    var external = (host.hostname !== this.hostname) ||
1259
+                   (host.port !== this.port) ||
1260
+                   (host.protocol !== this.protocol);
1261
+
1262
+    return !external;
1263
+  },
1264
+
1265
+  toURL: function() {
1266
+    var query = this.queryString();
1267
+    return this.protocol + '//' + this.hostname + (this.port ? ':' + this.port : '') +
1268
+           this.pathname + (query ? '?' + query : '') + this.hash;
1269
+  }
1270
+}), {
1271
+  parse: function(url, params) {
1272
+    if (typeof url !== 'string') return url;
1273
+    var uri = new this(), parts;
1274
+
1275
+    var consume = function(name, pattern, infer) {
1276
+      url = url.replace(pattern, function(match) {
1277
+        uri[name] = match;
1278
+        return '';
1279
+      });
1280
+      if (uri[name] === undefined)
1281
+        uri[name] = infer ? Faye.ENV.location[name] : '';
1282
+    };
1283
+
1284
+    consume('protocol', /^https?\:/,    true);
1285
+    consume('host',     /^\/\/[^\/]+/,  true);
1286
+
1287
+    if (!/^\//.test(url)) url = Faye.ENV.location.pathname.replace(/[^\/]*$/, '') + url;
1288
+    consume('pathname', /^\/[^\?#]*/);
1289
+    consume('search',   /^\?[^#]*/);
1290
+    consume('hash',     /^#.*/);
1291
+
1292
+    if (/^\/\//.test(uri.host)) {
1293
+      uri.host = uri.host.substr(2);
1294
+      parts = uri.host.split(':');
1295
+      uri.hostname = parts[0];
1296
+      uri.port = parts[1] || '';
1297
+    } else {
1298
+      uri.hostname = Faye.ENV.location.hostname;
1299
+      uri.port = Faye.ENV.location.port;
1300
+    }
1301
+
1302
+    if (params === false) {
1303
+      uri.params = {};
1304
+    } else {
1305
+      var query = uri.search.replace(/^\?/, ''),
1306
+          pairs = query ? query.split('&') : [],
1307
+          n     = pairs.length,
1308
+          data  = {};
1309
+
1310
+      while (n--) {
1311
+        parts = pairs[n].split('=');
1312
+        data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || '');
1313
+      }
1314
+      if (typeof params === 'object') Faye.extend(data, params);
1315
+
1316
+      uri.params = data;
1317
+    }
1318
+
1319
+    return uri;
1320
+  }
1321
+});
1322
+
1323
+
1324
+/*
1325
+    http://www.JSON.org/json2.js
1326
+    2009-04-16
1327
+
1328
+    Public Domain.
1329
+
1330
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
1331
+
1332
+    See http://www.JSON.org/js.html
1333
+
1334
+    This file creates a global JSON object containing two methods: stringify
1335
+    and parse.
1336
+
1337
+        JSON.stringify(value, replacer, space)
1338
+            value       any JavaScript value, usually an object or array.
1339
+
1340
+            replacer    an optional parameter that determines how object
1341
+                        values are stringified for objects. It can be a
1342
+                        function or an array of strings.
1343
+
1344
+            space       an optional parameter that specifies the indentation
1345
+                        of nested structures. If it is omitted, the text will
1346
+                        be packed without extra whitespace. If it is a number,
1347
+                        it will specify the number of spaces to indent at each
1348
+                        level. If it is a string (such as '\t' or '&nbsp;'),
1349
+                        it contains the characters used to indent at each level.
1350
+
1351
+            This method produces a JSON text from a JavaScript value.
1352
+
1353
+            When an object value is found, if the object contains a toJSON
1354
+            method, its toJSON method will be called and the result will be
1355
+            stringified. A toJSON method does not serialize: it returns the
1356
+            value represented by the name/value pair that should be serialized,
1357
+            or undefined if nothing should be serialized. The toJSON method
1358
+            will be passed the key associated with the value, and this will be
1359
+            bound to the object holding the key.
1360
+
1361
+            For example, this would serialize Dates as ISO strings.
1362
+
1363
+                Date.prototype.toJSON = function (key) {
1364
+                    function f(n) {
1365
+                        // Format integers to have at least two digits.
1366
+                        return n < 10 ? '0' + n : n;
1367
+                    }
1368
+
1369
+                    return this.getUTCFullYear()   + '-' +
1370
+                         f(this.getUTCMonth() + 1) + '-' +
1371
+                         f(this.getUTCDate())      + 'T' +
1372
+                         f(this.getUTCHours())     + ':' +
1373
+                         f(this.getUTCMinutes())   + ':' +
1374
+                         f(this.getUTCSeconds())   + 'Z';
1375
+                };
1376
+
1377
+            You can provide an optional replacer method. It will be passed the
1378
+            key and value of each member, with this bound to the containing
1379
+            object. The value that is returned from your method will be
1380
+            serialized. If your method returns undefined, then the member will
1381
+            be excluded from the serialization.
1382
+
1383
+            If the replacer parameter is an array of strings, then it will be
1384
+            used to select the members to be serialized. It filters the results
1385
+            such that only members with keys listed in the replacer array are
1386
+            stringified.
1387
+
1388
+            Values that do not have JSON representations, such as undefined or
1389
+            functions, will not be serialized. Such values in objects will be
1390
+            dropped; in arrays they will be replaced with null. You can use
1391
+            a replacer function to replace those with JSON values.
1392
+            JSON.stringify(undefined) returns undefined.
1393
+
1394
+            The optional space parameter produces a stringification of the
1395
+            value that is filled with line breaks and indentation to make it
1396
+            easier to read.
1397
+
1398
+            If the space parameter is a non-empty string, then that string will
1399
+            be used for indentation. If the space parameter is a number, then
1400
+            the indentation will be that many spaces.
1401
+
1402
+            Example:
1403
+
1404
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
1405
+            // text is '["e",{"pluribus":"unum"}]'
1406
+
1407
+
1408
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
1409
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
1410
+
1411
+            text = JSON.stringify([new Date()], function (key, value) {
1412
+                return this[key] instanceof Date ?
1413
+                    'Date(' + this[key] + ')' : value;
1414
+            });
1415
+            // text is '["Date(---current time---)"]'
1416
+
1417
+
1418
+        JSON.parse(text, reviver)
1419
+            This method parses a JSON text to produce an object or array.
1420
+            It can throw a SyntaxError exception.
1421
+
1422
+            The optional reviver parameter is a function that can filter and
1423
+            transform the results. It receives each of the keys and values,
1424
+            and its return value is used instead of the original value.
1425
+            If it returns what it received, then the structure is not modified.
1426
+            If it returns undefined then the member is deleted.
1427
+
1428
+            Example:
1429
+
1430
+            // Parse the text. Values that look like ISO date strings will
1431
+            // be converted to Date objects.
1432
+
1433
+            myData = JSON.parse(text, function (key, value) {
1434
+                var a;
1435
+                if (typeof value === 'string') {
1436
+                    a =
1437
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
1438
+                    if (a) {
1439
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
1440
+                            +a[5], +a[6]));
1441
+                    }
1442
+                }
1443
+                return value;
1444
+            });
1445
+
1446
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
1447
+                var d;
1448
+                if (typeof value === 'string' &&
1449
+                        value.slice(0, 5) === 'Date(' &&
1450
+                        value.slice(-1) === ')') {
1451
+                    d = new Date(value.slice(5, -1));
1452
+                    if (d) {
1453
+                        return d;
1454
+                    }
1455
+                }
1456
+                return value;
1457
+            });
1458
+
1459
+
1460
+    This is a reference implementation. You are free to copy, modify, or
1461
+    redistribute.
1462
+
1463
+    This code should be minified before deployment.
1464
+    See http://javascript.crockford.com/jsmin.html
1465
+
1466
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
1467
+    NOT CONTROL.
1468
+*/
1469
+
1470
+/*jslint evil: true */
1471
+
1472
+/*global JSON */
1473
+
1474
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
1475
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
1476
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
1477
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
1478
+    test, toJSON, toString, valueOf
1479
+*/
1480
+
1481
+// Create a JSON object only if one does not already exist. We create the
1482
+// methods in a closure to avoid creating global variables.
1483
+
1484
+if (!this.JSON) {
1485
+    JSON = {};
1486
+}
1487
+(function () {
1488
+
1489
+    function f(n) {
1490
+        // Format integers to have at least two digits.
1491
+        return n < 10 ? '0' + n : n;
1492
+    }
1493
+
1494
+    if (typeof Date.prototype.toJSON !== 'function') {
1495
+
1496
+        Date.prototype.toJSON = function (key) {
1497
+
1498
+            return this.getUTCFullYear()   + '-' +
1499
+                 f(this.getUTCMonth() + 1) + '-' +
1500
+                 f(this.getUTCDate())      + 'T' +
1501
+                 f(this.getUTCHours())     + ':' +
1502
+                 f(this.getUTCMinutes())   + ':' +
1503
+                 f(this.getUTCSeconds())   + 'Z';
1504
+        };
1505
+
1506
+        String.prototype.toJSON =
1507
+        Number.prototype.toJSON =
1508
+        Boolean.prototype.toJSON = function (key) {
1509
+            return this.valueOf();
1510
+        };
1511
+    }
1512
+
1513
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
1514
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
1515
+        gap,
1516
+        indent,
1517
+        meta = {    // table of character substitutions
1518
+            '\b': '\\b',
1519
+            '\t': '\\t',
1520
+            '\n': '\\n',
1521
+            '\f': '\\f',
1522
+            '\r': '\\r',
1523
+            '"' : '\\"',
1524
+            '\\': '\\\\'
1525
+        },
1526
+        rep;
1527
+
1528
+
1529
+    function quote(string) {
1530
+
1531
+// If the string contains no control characters, no quote characters, and no
1532
+// backslash characters, then we can safely slap some quotes around it.
1533
+// Otherwise we must also replace the offending characters with safe escape
1534
+// sequences.
1535
+
1536
+        escapable.lastIndex = 0;
1537
+        return escapable.test(string) ?
1538
+            '"' + string.replace(escapable, function (a) {
1539
+                var c = meta[a];
1540
+                return typeof c === 'string' ? c :
1541
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
1542
+            }) + '"' :
1543
+            '"' + string + '"';
1544
+    }
1545
+
1546
+
1547
+    function str(key, holder) {
1548
+
1549
+// Produce a string from holder[key].
1550
+
1551
+        var i,          // The loop counter.
1552
+            k,          // The member key.
1553
+            v,          // The member value.
1554
+            length,
1555
+            mind = gap,
1556
+            partial,
1557
+            value = holder[key];
1558
+
1559
+// If the value has a toJSON method, call it to obtain a replacement value.
1560
+
1561
+        if (value && typeof value === 'object' &&
1562
+                typeof value.toJSON === 'function') {
1563
+            value = value.toJSON(key);
1564
+        }
1565
+
1566
+// If we were called with a replacer function, then call the replacer to
1567
+// obtain a replacement value.
1568
+
1569
+        if (typeof rep === 'function') {
1570
+            value = rep.call(holder, key, value);
1571
+        }
1572
+
1573
+// What happens next depends on the value's type.
1574
+
1575
+        switch (typeof value) {
1576
+        case 'string':
1577
+            return quote(value);
1578
+
1579
+        case 'number':
1580
+
1581
+// JSON numbers must be finite. Encode non-finite numbers as null.
1582
+
1583
+            return isFinite(value) ? String(value) : 'null';
1584
+
1585
+        case 'boolean':
1586
+        case 'null':
1587
+
1588
+// If the value is a boolean or null, convert it to a string. Note:
1589
+// typeof null does not produce 'null'. The case is included here in
1590
+// the remote chance that this gets fixed someday.
1591
+
1592
+            return String(value);
1593
+
1594
+// If the type is 'object', we might be dealing with an object or an array or
1595
+// null.
1596
+
1597
+        case 'object':
1598
+
1599
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
1600
+// so watch out for that case.
1601
+
1602
+            if (!value) {
1603
+                return 'null';
1604
+            }
1605
+
1606
+// Make an array to hold the partial results of stringifying this object value.
1607
+
1608
+            gap += indent;
1609
+            partial = [];
1610
+
1611
+// Is the value an array?
1612
+
1613
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
1614
+
1615
+// The value is an array. Stringify every element. Use null as a placeholder
1616
+// for non-JSON values.
1617
+
1618
+                length = value.length;
1619
+                for (i = 0; i < length; i += 1) {
1620
+                    partial[i] = str(i, value) || 'null';
1621
+                }
1622
+
1623
+// Join all of the elements together, separated with commas, and wrap them in
1624
+// brackets.
1625
+
1626
+                v = partial.length === 0 ? '[]' :
1627
+                    gap ? '[\n' + gap +
1628
+                            partial.join(',\n' + gap) + '\n' +
1629
+                                mind + ']' :
1630
+                          '[' + partial.join(',') + ']';
1631
+                gap = mind;
1632
+                return v;
1633
+            }
1634
+
1635
+// If the replacer is an array, use it to select the members to be stringified.
1636
+
1637
+            if (rep && typeof rep === 'object') {
1638
+                length = rep.length;
1639
+                for (i = 0; i < length; i += 1) {
1640
+                    k = rep[i];
1641
+                    if (typeof k === 'string') {
1642
+                        v = str(k, value);
1643
+                        if (v) {
1644
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
1645
+                        }
1646
+                    }
1647
+                }
1648
+            } else {
1649
+
1650
+// Otherwise, iterate through all of the keys in the object.
1651
+
1652
+                for (k in value) {
1653
+                    if (Object.hasOwnProperty.call(value, k)) {
1654
+                        v = str(k, value);
1655
+                        if (v) {
1656
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
1657
+                        }
1658
+                    }
1659
+                }
1660
+            }
1661
+
1662
+// Join all of the member texts together, separated with commas,
1663
+// and wrap them in braces.
1664
+
1665
+            v = partial.length === 0 ? '{}' :
1666
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
1667
+                        mind + '}' : '{' + partial.join(',') + '}';
1668
+            gap = mind;
1669
+            return v;
1670
+        }
1671
+    }
1672
+
1673
+// If the JSON object does not yet have a stringify method, give it one.
1674
+
1675
+// NOTE we've hacked this to expose this method to Faye. We need to use this
1676
+// to avoid problems with buggy Firefox version and bad #toJSON implementations
1677
+
1678
+        Faye.stringify = function (value, replacer, space) {
1679
+
1680
+// The stringify method takes a value and an optional replacer, and an optional
1681
+// space parameter, and returns a JSON text. The replacer can be a function
1682
+// that can replace values, or an array of strings that will select the keys.
1683
+// A default replacer method can be provided. Use of the space parameter can
1684
+// produce text that is more easily readable.
1685
+
1686
+            var i;
1687
+            gap = '';
1688
+            indent = '';
1689
+
1690
+// If the space parameter is a number, make an indent string containing that
1691
+// many spaces.
1692
+
1693
+            if (typeof space === 'number') {
1694
+                for (i = 0; i < space; i += 1) {
1695
+                    indent += ' ';
1696
+                }
1697
+
1698
+// If the space parameter is a string, it will be used as the indent string.
1699
+
1700
+            } else if (typeof space === 'string') {
1701
+                indent = space;
1702
+            }
1703
+
1704
+// If there is a replacer, it must be a function or an array.
1705
+// Otherwise, throw an error.
1706
+
1707
+            rep = replacer;
1708
+            if (replacer && typeof replacer !== 'function' &&
1709
+                    (typeof replacer !== 'object' ||
1710
+                     typeof replacer.length !== 'number')) {
1711
+                throw new Error('JSON.stringify');
1712
+            }
1713
+
1714
+// Make a fake root object containing our value under the key of ''.
1715
+// Return the result of stringifying the value.
1716
+
1717
+            return str('', {'': value});
1718
+        };
1719
+
1720
+    if (typeof JSON.stringify !== 'function') {
1721
+        JSON.stringify = Faye.stringify;
1722
+    }
1723
+
1724
+
1725
+// If the JSON object does not yet have a parse method, give it one.
1726
+
1727
+    if (typeof JSON.parse !== 'function') {
1728
+        JSON.parse = function (text, reviver) {
1729
+
1730
+// The parse method takes a text and an optional reviver function, and returns
1731
+// a JavaScript value if the text is a valid JSON text.
1732
+
1733
+            var j;
1734
+
1735
+            function walk(holder, key) {
1736
+
1737
+// The walk method is used to recursively walk the resulting structure so
1738
+// that modifications can be made.
1739
+
1740
+                var k, v, value = holder[key];
1741
+                if (value && typeof value === 'object') {
1742
+                    for (k in value) {
1743
+                        if (Object.hasOwnProperty.call(value, k)) {
1744
+                            v = walk(value, k);
1745
+                            if (v !== undefined) {
1746
+                                value[k] = v;
1747
+                            } else {
1748
+                                delete value[k];
1749
+                            }
1750
+                        }
1751
+                    }
1752
+                }
1753
+                return reviver.call(holder, key, value);
1754
+            }
1755
+
1756
+
1757
+// Parsing happens in four stages. In the first stage, we replace certain
1758
+// Unicode characters with escape sequences. JavaScript handles many characters
1759
+// incorrectly, either silently deleting them, or treating them as line endings.
1760
+
1761
+            cx.lastIndex = 0;
1762
+            if (cx.test(text)) {
1763
+                text = text.replace(cx, function (a) {
1764
+                    return '\\u' +
1765
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
1766
+                });
1767
+            }
1768
+
1769
+// In the second stage, we run the text against regular expressions that look
1770
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
1771
+// because they can cause invocation, and '=' because it can cause mutation.
1772
+// But just to be safe, we want to reject all unexpected forms.
1773
+
1774
+// We split the second stage into 4 regexp operations in order to work around
1775
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
1776
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
1777
+// replace all simple value tokens with ']' characters. Third, we delete all
1778
+// open brackets that follow a colon or comma or that begin the text. Finally,
1779
+// we look to see that the remaining characters are only whitespace or ']' or
1780
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
1781
+
1782
+            if (/^[\],:{}\s]*$/.
1783
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
1784
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
1785
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
1786
+
1787
+// In the third stage we use the eval function to compile the text into a
1788
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
1789
+// in JavaScript: it can begin a block or an object literal. We wrap the text
1790
+// in parens to eliminate the ambiguity.
1791
+
1792
+                j = eval('(' + text + ')');
1793
+
1794
+// In the optional fourth stage, we recursively walk the new structure, passing
1795
+// each name/value pair to a reviver function for possible transformation.
1796
+
1797
+                return typeof reviver === 'function' ?
1798
+                    walk({'': j}, '') : j;
1799
+            }
1800
+
1801
+// If the text is not JSON parseable, then a SyntaxError is thrown.
1802
+
1803
+            throw new SyntaxError('JSON.parse');
1804
+        };
1805
+    }
1806
+}());
1807
+
1808
+
1809
+Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
1810
+  UNCONNECTED:  1,
1811
+  CONNECTING:   2,
1812
+  CONNECTED:    3,
1813
+
1814
+  batching:     false,
1815
+
1816
+  isUsable: function(callback, context) {
1817
+    this.callback(function() { callback.call(context, true) });
1818
+    this.errback(function() { callback.call(context, false) });
1819
+    this.connect();
1820
+  },
1821
+
1822
+  request: function(messages, timeout) {
1823
+    if (messages.length === 0) return;
1824
+    this._messages = this._messages || {};
1825
+
1826
+    for (var i = 0, n = messages.length; i < n; i++) {
1827
+      this._messages[messages[i].id] = messages[i];
1828
+    }
1829
+    this.callback(function(socket) { socket.send(Faye.toJSON(messages)) });
1830
+    this.connect();
1831
+  },
1832
+
1833
+  close: function() {
1834
+    if (!this._socket) return;
1835
+    this._socket.onclose = this._socket.onerror = null;
1836
+    this._socket.close();
1837
+    delete this._socket;
1838
+    this.setDeferredStatus('deferred');
1839
+    this._state = this.UNCONNECTED;
1840
+  },
1841
+
1842
+  connect: function() {
1843
+    if (Faye.Transport.WebSocket._unloaded) return;
1844
+
1845
+    this._state = this._state || this.UNCONNECTED;
1846
+    if (this._state !== this.UNCONNECTED) return;
1847
+
1848
+    this._state = this.CONNECTING;
1849
+
1850
+    var ws = Faye.Transport.WebSocket.getClass();
1851
+    if (!ws) return this.setDeferredStatus('failed');
1852
+
1853
+    this._socket = new ws(Faye.Transport.WebSocket.getSocketUrl(this.endpoint));
1854
+    var self = this;
1855
+
1856
+    this._socket.onopen = function() {
1857
+      self._state = self.CONNECTED;
1858
+      self._everConnected = true;
1859
+      self.setDeferredStatus('succeeded', self._socket);
1860
+      self.trigger('up');
1861
+    };
1862
+
1863
+    this._socket.onmessage = function(event) {
1864
+      var messages = JSON.parse(event.data);
1865
+      if (!messages) return;
1866
+      messages = [].concat(messages);
1867
+
1868
+      for (var i = 0, n = messages.length; i < n; i++) {
1869
+        delete self._messages[messages[i].id];
1870
+      }
1871
+      self.receive(messages);
1872
+    };
1873
+
1874
+    this._socket.onclose = this._socket.onerror = function() {
1875
+      var wasConnected = (self._state === self.CONNECTED);
1876
+      self.setDeferredStatus('deferred');
1877
+      self._state = self.UNCONNECTED;
1878
+
1879
+      self.close();
1880
+
1881
+      if (wasConnected) return self.resend();
1882
+      if (!self._everConnected) return self.setDeferredStatus('failed');
1883
+
1884
+      var retry = self._client.retry * 1000;
1885
+      Faye.ENV.setTimeout(function() { self.connect() }, retry);
1886
+      self.trigger('down');
1887
+    };
1888
+  },
1889
+
1890
+  resend: function() {
1891
+    if (!this._messages) return;
1892
+    var messages = Faye.map(this._messages, function(id, msg) { return msg });
1893
+    this.request(messages);
1894
+  }
1895
+}), {
1896
+  getSocketUrl: function(endpoint) {
1897
+    if (Faye.URI) endpoint = Faye.URI.parse(endpoint).toURL();
1898
+    return endpoint.replace(/^http(s?):/ig, 'ws$1:');
1899
+  },
1900
+
1901
+  getClass: function() {
1902
+    return (Faye.WebSocket && Faye.WebSocket.Client) ||
1903
+            Faye.ENV.WebSocket ||
1904
+            Faye.ENV.MozWebSocket;
1905
+  },
1906
+
1907
+  isUsable: function(client, endpoint, callback, context) {
1908
+    this.create(client, endpoint).isUsable(callback, context);
1909
+  },
1910
+
1911
+  create: function(client, endpoint) {
1912
+    var sockets = client.transports.websocket = client.transports.websocket || {};
1913
+    sockets[endpoint] = sockets[endpoint] || new this(client, endpoint);
1914
+    return sockets[endpoint];
1915
+  }
1916
+});
1917
+
1918
+Faye.extend(Faye.Transport.WebSocket.prototype, Faye.Deferrable);
1919
+Faye.Transport.register('websocket', Faye.Transport.WebSocket);
1920
+
1921
+if (Faye.Event)
1922
+  Faye.Event.on(Faye.ENV, 'beforeunload', function() {
1923
+    Faye.Transport.WebSocket._unloaded = true;
1924
+  });
1925
+
1926
+
1927
+Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, {
1928
+  initialize: function(client, endpoint) {
1929
+    Faye.Transport.prototype.initialize.call(this, client, endpoint);
1930
+    if (!Faye.ENV.EventSource) return this.setDeferredStatus('failed');
1931
+
1932
+    this._xhr = new Faye.Transport.XHR(client, endpoint);
1933
+
1934
+    var socket = new EventSource(endpoint + '/' + client.getClientId()),
1935
+        self   = this;
1936
+
1937
+    socket.onopen = function() {
1938
+      self._everConnected = true;
1939
+      self.setDeferredStatus('succeeded');
1940
+      self.trigger('up');
1941
+    };
1942
+
1943
+    socket.onerror = function() {
1944
+      if (self._everConnected) {
1945
+        self.trigger('down');
1946
+      } else {
1947
+        self.setDeferredStatus('failed');
1948
+        socket.close();
1949
+      }
1950
+    };
1951
+
1952
+    socket.onmessage = function(event) {
1953
+      self.receive(JSON.parse(event.data));
1954
+      self.trigger('up');
1955
+    };
1956
+
1957
+    this._socket = socket;
1958
+  },
1959
+
1960
+  isUsable: function(callback, context) {
1961
+    this.callback(function() { callback.call(context, true) });
1962
+    this.errback(function() { callback.call(context, false) });
1963
+  },
1964
+
1965
+  request: function(message, timeout) {
1966
+    this._xhr.request(message, timeout);
1967
+  },
1968
+
1969
+  close: function() {
1970
+    if (!this._socket) return;
1971
+    this._socket.onerror = null;
1972
+    this._socket.close();
1973
+    delete this._socket;
1974
+  }
1975
+}), {
1976
+  isUsable: function(client, endpoint, callback, context) {
1977
+    var id = client.getClientId();
1978
+    if (!id) return callback.call(context, false);
1979
+
1980
+    Faye.Transport.XHR.isUsable(client, endpoint, function(usable) {
1981
+      if (!usable) return callback.call(context, false);
1982
+      this.create(client, endpoint).isUsable(callback, context);
1983
+    }, this);
1984
+  },
1985
+
1986
+  create: function(client, endpoint) {
1987
+    var sockets  = client.transports.eventsource = client.transports.eventsource || {},
1988
+        id       = client.getClientId(),
1989
+        endpoint = endpoint + '/' + (id || '');
1990
+
1991
+    sockets[endpoint] = sockets[endpoint] || new this(client, endpoint);
1992
+    return sockets[endpoint];
1993
+  }
1994
+});
1995
+
1996
+Faye.extend(Faye.Transport.EventSource.prototype, Faye.Deferrable);
1997
+Faye.Transport.register('eventsource', Faye.Transport.EventSource);
1998
+
1999
+
2000
+Faye.Transport.XHR = Faye.extend(Faye.Class(Faye.Transport, {
2001
+  request: function(message, timeout) {
2002
+    var retry = this.retry(message, timeout),
2003
+        path  = Faye.URI.parse(this.endpoint).pathname,
2004
+        self  = this,
2005
+        xhr   = Faye.ENV.ActiveXObject
2006
+              ? new ActiveXObject("Microsoft.XMLHTTP")
2007
+              : new XMLHttpRequest();
2008
+
2009
+    xhr.open('POST', path, true);
2010
+    xhr.setRequestHeader('Content-Type', 'application/json');
2011
+    xhr.setRequestHeader('Pragma', 'no-cache');
2012
+    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2013
+
2014
+    var headers = this.headers;
2015
+    for (var key in headers) {
2016
+      if (!headers.hasOwnProperty(key)) continue;
2017
+      xhr.setRequestHeader(key, headers[key]);
2018
+    }
2019
+
2020
+    var abort = function() { xhr.abort() };
2021
+    Faye.Event.on(Faye.ENV, 'beforeunload', abort);
2022
+
2023
+    var cleanUp = function() {
2024
+      Faye.Event.detach(Faye.ENV, 'beforeunload', abort);
2025
+      xhr.onreadystatechange = function() {};
2026
+      xhr = null;
2027
+    };
2028
+
2029
+    xhr.onreadystatechange = function() {
2030
+      if (xhr.readyState !== 4) return;
2031
+
2032
+      var parsedMessage = null,
2033
+          status        = xhr.status,
2034
+          successful    = ((status >= 200 && status < 300) ||
2035
+                            status === 304 ||
2036
+                            status === 1223);
2037
+
2038
+      if (!successful) {
2039
+        cleanUp();
2040
+        retry();
2041
+        return self.trigger('down');
2042
+      }
2043
+
2044
+      try {
2045
+        parsedMessage = JSON.parse(xhr.responseText);
2046
+      } catch (e) {}
2047
+
2048
+      cleanUp();
2049
+
2050
+      if (parsedMessage) {
2051
+        self.receive(parsedMessage);
2052
+        self.trigger('up');
2053
+      } else {
2054
+        retry();
2055
+        self.trigger('down');
2056
+      }
2057
+    };
2058
+
2059
+    xhr.send(Faye.toJSON(message));
2060
+  }
2061
+}), {
2062
+  isUsable: function(client, endpoint, callback, context) {
2063
+    callback.call(context, Faye.URI.parse(endpoint).isSameOrigin());
2064
+  }
2065
+});
2066
+
2067
+Faye.Transport.register('long-polling', Faye.Transport.XHR);
2068
+
2069
+Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, {
2070
+  request: function(message, timeout) {
2071
+    var xhrClass = Faye.ENV.XDomainRequest ? XDomainRequest : XMLHttpRequest,
2072
+        xhr      = new xhrClass(),
2073
+        retry    = this.retry(message, timeout),
2074
+        self     = this;
2075
+
2076
+    xhr.open('POST', this.endpoint, true);
2077
+    if (xhr.setRequestHeader) xhr.setRequestHeader('Pragma', 'no-cache');
2078
+
2079
+    var cleanUp = function() {
2080
+      if (!xhr) return false;
2081
+      xhr.onload = xhr.onerror = xhr.ontimeout = xhr.onprogress = null;
2082
+      xhr = null;
2083
+      Faye.ENV.clearTimeout(timer);
2084
+      return true;
2085
+    };
2086
+
2087
+    xhr.onload = function() {
2088
+      var parsedMessage = null;
2089
+      try {
2090
+        parsedMessage = JSON.parse(xhr.responseText);
2091
+      } catch (e) {}
2092
+
2093
+      cleanUp();
2094
+
2095
+      if (parsedMessage) {
2096
+        self.receive(parsedMessage);
2097
+        self.trigger('up');
2098
+      } else {
2099
+        retry();
2100
+        self.trigger('down');
2101
+      }
2102
+    };
2103
+
2104
+    var onerror = function() {
2105
+      cleanUp();
2106
+      retry();
2107
+      self.trigger('down');
2108
+    };
2109
+    var timer = Faye.ENV.setTimeout(onerror, 1.5 * 1000 * timeout);
2110
+    xhr.onerror = onerror;
2111
+    xhr.ontimeout = onerror;
2112
+
2113
+    xhr.onprogress = function() {};
2114
+    xhr.send('message=' + encodeURIComponent(Faye.toJSON(message)));
2115
+  }
2116
+}), {
2117
+  isUsable: function(client, endpoint, callback, context) {
2118
+    if (Faye.URI.parse(endpoint).isSameOrigin())
2119
+      return callback.call(context, false);
2120
+
2121
+    if (Faye.ENV.XDomainRequest)
2122
+      return callback.call(context, Faye.URI.parse(endpoint).protocol ===
2123
+                                    Faye.URI.parse(Faye.ENV.location).protocol);
2124
+
2125
+    if (Faye.ENV.XMLHttpRequest) {
2126
+      var xhr = new Faye.ENV.XMLHttpRequest();
2127
+      return callback.call(context, xhr.withCredentials !== undefined);
2128
+    }
2129
+    return callback.call(context, false);
2130
+  }
2131
+});
2132
+
2133
+Faye.Transport.register('cross-origin-long-polling', Faye.Transport.CORS);
2134
+
2135
+
2136
+Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, {
2137
+  shouldFlush: function(messages) {
2138
+    var params = {
2139
+      message:  Faye.toJSON(messages),
2140
+      jsonp:    '__jsonp' + Faye.Transport.JSONP._cbCount + '__'
2141
+    };
2142
+    var location = Faye.URI.parse(this.endpoint, params).toURL();
2143
+    return location.length >= Faye.Transport.MAX_URL_LENGTH;
2144
+  },
2145
+
2146
+  request: function(messages, timeout) {
2147
+    var params       = {message: Faye.toJSON(messages)},
2148
+        head         = document.getElementsByTagName('head')[0],
2149
+        script       = document.createElement('script'),
2150
+        callbackName = Faye.Transport.JSONP.getCallbackName(),
2151
+        location     = Faye.URI.parse(this.endpoint, params),
2152
+        retry        = this.retry(messages, timeout),
2153
+        self         = this;
2154
+
2155
+    Faye.ENV[callbackName] = function(data) {
2156
+      cleanUp();
2157
+      self.receive(data);
2158
+      self.trigger('up');
2159
+    };
2160
+
2161
+    var timer = Faye.ENV.setTimeout(function() {
2162
+      cleanUp();
2163
+      retry();
2164
+      self.trigger('down');
2165
+    }, 1.5 * 1000 * timeout);
2166
+
2167
+    var cleanUp = function() {
2168
+      if (!Faye.ENV[callbackName]) return false;
2169
+      Faye.ENV[callbackName] = undefined;
2170
+      try { delete Faye.ENV[callbackName] } catch (e) {}
2171
+      Faye.ENV.clearTimeout(timer);
2172
+      script.parentNode.removeChild(script);
2173
+      return true;
2174
+    };
2175
+
2176
+    location.params.jsonp = callbackName;
2177
+    script.type = 'text/javascript';
2178
+    script.src  = location.toURL();
2179
+    head.appendChild(script);
2180
+  }
2181
+}), {
2182
+  _cbCount: 0,
2183
+
2184
+  getCallbackName: function() {
2185
+    this._cbCount += 1;
2186
+    return '__jsonp' + this._cbCount + '__';
2187
+  },
2188
+
2189
+  isUsable: function(client, endpoint, callback, context) {
2190
+    callback.call(context, true);
2191
+  }
2192
+});
2193
+
2194
+Faye.Transport.register('callback-polling', Faye.Transport.JSONP);

+ 11 - 11
app/scripts/services/page-service.js

@@ -3,16 +3,16 @@ angular.module('avalancheDocsApp')
3 3
   var pages = [];
4 4
   var current_page = {};
5 5
 
6
-  this.find = function(page){
6
+  this.find = function(group, page){
7 7
     if( pages.length > 0) {
8
-      searchForPage(page);
8
+      searchForPage(group, page);
9 9
     } else {
10 10
       $http({
11 11
         method: 'GET',
12
-        url: 'data/rest-api-v1-pages.json'
12
+        url: 'data/page-list.json'
13 13
       }).then(function(data) {
14 14
         pages = data.data;
15
-        searchForPage(page);
15
+        searchForPage(group, page);
16 16
         $rootScope.$broadcast('get-pages:finished');
17 17
       });
18 18
     }
@@ -22,16 +22,16 @@ angular.module('avalancheDocsApp')
22 22
     return current_page;
23 23
   }
24 24
 
25
-  this.all = function(){
26
-    return pages;
25
+  this.all = function(group){
26
+    return pages[group];
27 27
   }
28 28
 
29
-  searchForPage = function(page) {
29
+  searchForPage = function(group, page) {
30 30
     //console.log("searching for " + page);
31
-    for (var i = 0; i < pages.length; i++) {
32
-      //console.log(pages[i].slug);
33
-      if (pages[i].slug == page) {
34
-        current_page = pages[i];
31
+    for (var i = 0; i < pages[group].length; i++) {
32
+      //console.log(pages[group][i].slug);
33
+      if (pages[group][i].slug == page) {
34
+        current_page = pages[group][i];
35 35
         return current_page;
36 36
       }
37 37
     }

+ 37 - 0
app/scripts/services/realtime-service.js

@@ -0,0 +1,37 @@
1
+angular.module('avalancheDocsApp')
2
+.factory('RealtimeService', [ '$rootScope', '$http',  function($rootScope, $http) {
3
+  var FayeServerURL = 'https://avalanche-realtime.herokuapp.com/faye'
4
+
5
+  var client = new Faye.Client(FayeServerURL);
6
+
7
+  client.on('transport:down', function() {
8
+    console.log("offline");
9
+    $rootScope.$broadcast('realtime:offline');
10
+  });
11
+
12
+  client.on('transport:up', function() {
13
+    console.log("online");
14
+    $rootScope.$broadcast('realtime:online');
15
+  });
16
+
17
+  return {
18
+    publish: function(channel, message) {
19
+      client.publish(channel, message);
20
+    },
21
+
22
+    subscribe: function(channel, callback) {
23
+      client.subscribe(channel, callback).then(function() {
24
+        console.log("subscribing to " + channel)
25
+      });
26
+    },
27
+    disconect: function() {
28
+      console.log("Disconecting...")
29
+      client.disconnect();
30
+      $rootScope.$broadcast('realtime:offline');
31
+    },
32
+    connect: function() {
33
+      client = new Faye.Client(FayeServerURL);
34
+      $rootScope.$broadcast('realtime:online');
35
+    }
36
+  }
37
+}]);

+ 55 - 0
app/styles/avalanche_theme/docs.scss

@@ -135,3 +135,58 @@
135 135
 ::-webkit-scrollbar-thumb {
136 136
     background-color: #AFB4BF;
137 137
 }
138
+
139
+.angular-json-explorer {
140
+    font-family: monospace;
141
+    font-size: 1em;
142
+    white-space: pre-wrap;
143
+}
144
+
145
+.circle-ok {
146
+  width: 10px;
147
+  background-color: #5FB760;
148
+  border-radius: 50px;
149
+  height: 10px;
150
+  float: left;
151
+  margin-top: 8px;
152
+  margin-right: 5px;
153
+}
154
+
155
+.circle-error {
156
+  width: 10px;
157
+  background-color: #D75452;
158
+  border-radius: 50px;
159
+  height: 10px;
160
+  float: left;
161
+  margin-top: 8px;
162
+  margin-right: 5px;
163
+}
164
+
165
+.bd-callout .bd-callout {
166
+    margin-top: -.25rem;
167
+}
168
+
169
+.bd-callout {
170
+    padding: 1.25rem;
171
+    margin-top: 1.25rem;
172
+    margin-bottom: 1.25rem;
173
+    border: 1px solid #eee;
174
+    border-left-width: .25rem;
175
+    border-radius: .25rem;
176
+}
177
+
178
+.bd-callout.bd-callout-danger {
179
+    border-left-color: #d9534f;
180
+}
181
+
182
+.bd-callout-danger h4 {
183
+    color: #d9534f;
184
+}
185
+.bd-callout h4 {
186
+    margin-top: 0;
187
+    margin-bottom: .25rem;
188
+}
189
+
190
+.bd-callout p:last-child {
191
+    margin-bottom: 0;
192
+}

BIN
app/views/.DS_Store


+ 30 - 11
app/views/getting-started.html

@@ -1,17 +1,36 @@
1 1
 <div class="container-fluid" >
2 2
   <div class="row">
3
-    <div class="col-sm-3 col-md-2 sidebar sidebar-main">
4
-      <h1>docs</h1>
5
-      <ul class="nav nav-sidebar">
6
-        <li class="active"><a ui-sref="getting-started">Getting Started<span class="sr-only">(current)</span></a></li>
7
-        <li><a ui-sref="rulebook.intro">Rulebook</a></li>
8
-        <li><a ui-sref="styleguide">Styleguide</a></li>
9
-        <li><a ui-sref="rest-api-v1({ id: 'get-missions' })">Restfull API v1 </a></li>
10
-        <li><a ui-sref="realtime-api-v1">Realtime API v1</a></li>
11
-      </ul>
12
-    </div>
3
+    <ng-include src="'views/helpers/sidebar.html'"></ng-include>
13 4
     <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" ui-view>
14
-
5
+      <div class="jumbotron jumbotron-fluid" style="margin-left: -40px; margin-right: -40px; margin-top: -20px;">
6
+        <div class="container">
7
+          <h1 class="display-3">Avalanche Docs</h1>
8
+          <p class="lead">Welcome to the Avalanche Network documentation getting started guide.</p>
9
+        </div>
10
+      </div>
11
+      <div class="row">
12
+        <div class="col-sm-4">
13
+          <div class="card card-block">
14
+            <h3 class="card-title">Rulebook</h3>
15
+            <p class="card-text">Detailed information about all aspects of the Avalanhce Network mechanics.</p>
16
+            <a ui-sref="rulebook({ id: 'intro'})" class="btn btn-primary">View rulebook</a>
17
+          </div>
18
+        </div>
19
+        <div class="col-sm-4">
20
+          <div class="card card-block">
21
+            <h3 class="card-title">Styleguide</h3>
22
+            <p class="card-text">Avalanche logo, color pallet, UI Theme/template and the web font icon set.</p>
23
+            <a href="#" class="btn btn-primary">View styleguide</a>
24
+          </div>
25
+        </div>
26
+        <div class="col-sm-4">
27
+          <div class="card card-block">
28
+            <h3 class="card-title">Restfull API v1</h3>
29
+            <p class="card-text">Detailed information about each API endpoint with examples and tests.</p>
30
+            <a ui-sref="rest-api-v1({ id: 'get-missions' })" class="btn btn-primary">View API Docs</a>
31
+          </div>
32
+        </div>
33
+      </div>
15 34
     </div>
16 35
   </div>
17 36
 </div>

+ 1 - 1
app/views/helpers/api-response.html

@@ -4,7 +4,7 @@
4 4
       <td colspan="2">{{status(response.status).code}}</td>
5 5
     </tr>
6 6
     <tr>
7
-      <td colspan="2"><json-formatter json="response.data" open="1" style="overflow-x: hidden;"></json-formatter></td>
7
+      <td colspan="2"><json-explorer data="response.data"></json-explorer></td>
8 8
     </tr>
9 9
     <tr>
10 10
       <td>Method</td>

+ 14 - 0
app/views/helpers/sidebar.html

@@ -0,0 +1,14 @@
1
+<div class="col-sm-3 col-md-2 sidebar sidebar-main">
2
+  <h1>docs</h1>
3
+  <ul class="nav nav-sidebar">
4
+    <li ng-class="{ active: navActivePage('getting-started')}"><a ui-sref="getting-started">Getting Started</a></li>
5
+    <li ng-hide="'false'"><a ui-sref="tutorials">Tutorials</a></li>
6
+    <li ng-hide="'false'"><a ui-sref="manifest">Manifest</a></li>
7
+    <li ng-class="{ active: navActivePage('rulebook')}"><a ui-sref="rulebook({ id: 'intro'})">Rulebook</a></li>
8
+    <li ng-hide="'false'"><a ui-sref="faq">FAQ</a></li>
9
+    <li ng-hide="'false'"><a ui-sref="styleguide">Styleguide</a></li>
10
+    <li ng-hide="'true'"><a ui-sref="theme">Theme</a></li>
11
+    <li ng-class="{ active: navActivePage('rest-api-v1')}"><a ui-sref="rest-api-v1({ id: 'get-missions' })">Restfull API v1 <span class="sr-only">(current)</span></a></li>
12
+    <li ng-class="{ active: navActivePage('realtime-api-v1')}"><a ui-sref="realtime-api-v1({ id: 'realtime-mission-activity' })">Realtime API v1</a></li>
13
+  </ul>
14
+</div>

+ 5 - 0
app/views/helpers/under-construction.html

@@ -0,0 +1,5 @@
1
+<div class="bd-callout bd-callout-danger">
2
+<h4 id="cross-browser-compatibility">Not Implemented</h4>
3
+
4
+<p>This feature has not been implemented yet. It has been planed to be implemented soon. Hold tight.</p>
5
+</div>

+ 78 - 0
app/views/realtime-api-v1.html

@@ -0,0 +1,78 @@
1
+<div class="container-fluid" >
2
+  <div class="row">
3
+    <ng-include src="'views/helpers/sidebar.html'"></ng-include>
4
+    <div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary">
5
+      <ul class="nav nav-sidebar">
6
+        <li ng-repeat="page in pageList" ng-class="{ active: isActive(page.pageUrl)}">
7
+          <a ui-sref="realtime-api-v1({ id: page.slug})">
8
+            <h5>{{page.title}}</h5>
9
+            <p class="endpoint-description"><i>{{page.tagline}}</i></p>
10
+          </a>
11
+        </li>
12
+      </ul>
13
+    </div>
14
+    <div class="col-sm-5 col-sm-offset-7 col-md-7 col-md-offset-5 main">
15
+      <div class="api-endpoint">
16
+        <h2 style="margin-bottom: 0px;">{{pageData.title}}</h2>
17
+        <small class="api-endpoint"><span ng-class="callTypeBadge(pageData.endpoint.type)">{{pageData.endpoint.type}}</span> {{pageData.endpoint.base}}</small>
18
+        <hr>
19
+
20
+        <div ng-if="pageData.description != undefined">
21
+          <p ng-bind-html="pageData.description"></p>
22
+        </div>
23
+
24
+        <ng-include src="'views/helpers/under-construction.html'" ng-hide="pageData.implemented"></ng-include>
25
+
26
+        <div class="top-spacer" ng-show="pageData.implemented">
27
+          <h5>Subscribe to channel</h5>
28
+          <p class="top-spacer">
29
+            To subscribe, first get the mission chanel name using the restfull API and authentication scheme. After you have the mission chanel name, use faye to subscribe to that chanel.
30
+          </p>
31
+          <div  class="row top-spacer">
32
+              <div class="col-sm-9">
33
+                <fieldset class="form-group">
34
+                  <label for="mission-slug">Mission Channel Name</label>
35
+                  <input type="text" class="form-control" id="mission-slug" placeholder="/missions/example-mission" ng-model="MissionChanelName" ng-hide="has_connection">
36
+                  <input type="text" class="form-control" id="mission-slug" placeholder="/missions/example-mission" ng-model="MissionChanelName" ng-show="has_connection" disabled>
37
+                </fieldset>
38
+              </div>
39
+              <div class="col-sm-3">
40
+                <fieldset class="form-group" style="margin-top: 35px;">
41
+                  <button type="submit" class="btn btn-success btn-sm" ng-click="subscribe(MissionChanelName)" ng-hide="has_connection">Connect</button>
42
+                  <button type="submit" class="btn btn-danger-outline btn-sm" ng-click="disconect()" ng-show="has_connection">Disconect</button>
43
+                </fieldset>
44
+              </div>
45
+          </div>
46
+          <div  class="row" ng-show="subscribing">
47
+            <div class="table-responsive col-sm-12" >
48
+              <table class="table table-bordered top-spacer" >
49
+                <tr>
50
+                  <td colspan="2"><span ng-bind-html="connection_status"></span><i class="pull-right" ng-bind="messages.length + ' messages received'"></i></td>
51
+                </tr>
52
+                <tr>
53
+                  <td colspan="2"><json-explorer data="messages"></json-explorer></td>
54
+                </tr>
55
+              </table>
56
+            </div>
57
+          </div>
58
+          <div class="row" ng-show="subscribing && logged_in">
59
+            <div class="col-sm-9">
60
+              <fieldset class="form-group">
61
+                <label for="mission-slug">Chat message</label>
62
+                <input type="text" class="form-control" id="mission-slug" placeholder="" ng-model="chatMessage">
63
+              </fieldset>
64
+            </div>
65
+            <div class="col-sm-3">
66
+              <fieldset class="form-group" style="margin-top: 35px;">
67
+                <button type="submit" class="btn btn-success btn-sm" ng-click="getData(pageData.endpoint.base)">Send Msg</button>
68
+              </fieldset>
69
+            </div>
70
+          </div>
71
+      </div>
72
+
73
+
74
+
75
+
76
+    </div>
77
+  </div>
78
+</div>

+ 19 - 24
app/views/rest-api-v1.html

@@ -1,15 +1,6 @@
1 1
 <div class="container-fluid" >
2 2
   <div class="row">
3
-    <div class="col-sm-3 col-md-2 sidebar sidebar-main">
4
-      <h1>docs</h1>
5
-      <ul class="nav nav-sidebar">
6
-        <li><a ui-sref="getting-started">Getting Started</a></li>
7
-        <li><a ui-sref="rulebook.intro">Rulebook</a></li>
8
-        <li><a ui-sref="styleguide">Styleguide</a></li>
9
-        <li class="active"><a ui-sref="rest-api-v1({ id: 'get-missions' })">Restfull API v1 <span class="sr-only">(current)</span></a></li>
10
-        <li><a ui-sref="realtime-api-v1">Realtime API v1</a></li>
11
-      </ul>
12
-    </div>
3
+    <ng-include src="'views/helpers/sidebar.html'"></ng-include>
13 4
     <div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary">
14 5
       <ul class="nav nav-sidebar">
15 6
         <li ng-repeat="page in pageList" ng-class="{ active: isActive(page.pageUrl)}">
@@ -31,6 +22,8 @@
31 22
           <p ng-bind-html="pageData.description"></p>
32 23
         </div>
33 24
 
25
+        <ng-include src="'views/helpers/under-construction.html'" ng-hide="pageData.implemented"></ng-include>
26
+
34 27
         <div ng-if="pageData.variables.length > 0">
35 28
           <h5 class="top-spacer">Variables</h5>
36 29
           <table class="table table-bordered top-spacer">
@@ -50,20 +43,22 @@
50 43
           </tabset>
51 44
         </div>
52 45
 
53
-        <h5>Responses</h5>
54
-        <form class="top-spacer">
55
-          <ng-include src="'views/helpers/api-token-field.html'"></ng-include>
56
-          <div  class="row">
57
-              <div class="col-sm-3" ng-repeat="variable in pageData.variables">
58
-                <fieldset class="form-group">
59
-                  <label for="{{variable.name}}">{{variable.name}}</label>
60
-                  <input type="text" class="form-control" id="{{variable.name}}" placeholder="{{variable.placeholder}}" ng-model="variable.value">
61
-                </fieldset>
62
-              </div>
63
-          </div>
64
-          <ng-include src="'views/helpers/api-endpoint-field.html'"></ng-include>
65
-        </form>
66
-        <ng-include src="'views/helpers/api-response.html'"></ng-include>
46
+        <div ng-show="pageData.implemented">
47
+          <h5>Responses</h5>
48
+          <form class="top-spacer">
49
+            <ng-include src="'views/helpers/api-token-field.html'"></ng-include>
50
+            <div  class="row">
51
+                <div class="col-sm-3" ng-repeat="variable in pageData.variables">
52
+                  <fieldset class="form-group">
53
+                    <label for="{{variable.name}}">{{variable.name}}</label>
54
+                    <input type="text" class="form-control" id="{{variable.name}}" placeholder="{{variable.placeholder}}" ng-model="variable.value">
55
+                  </fieldset>
56
+                </div>
57
+            </div>
58
+            <ng-include src="'views/helpers/api-endpoint-field.html'"></ng-include>
59
+          </form>
60
+          <ng-include src="'views/helpers/api-response.html'"></ng-include>
61
+        </div>
67 62
 
68 63
     </div>
69 64
   </div>

+ 22 - 0
app/views/rulebook.html

@@ -0,0 +1,22 @@
1
+<div class="container-fluid" >
2
+  <div class="row">
3
+    <ng-include src="'views/helpers/sidebar.html'"></ng-include>
4
+    <div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary">
5
+      <ul class="nav nav-sidebar">
6
+        <li ng-repeat="page in pageList" ng-class="{ active: isActive(page.pageUrl)}">
7
+          <a ui-sref="rulebook({ id: page.slug})">
8
+            <h5>{{page.title}}</h5>
9
+            <p class="endpoint-description"><i>{{page.tagline}}</i></p>
10
+          </a>
11
+        </li>
12
+      </ul>
13
+    </div>
14
+    <div class="col-sm-5 col-sm-offset-7 col-md-7 col-md-offset-5 main" ui-view>
15
+      <div class="api-endpoint">
16
+        <h2 style="margin-bottom: 0px;">{{pageData.title}}</h2>
17
+        <hr>
18
+      </div>
19
+      <div btf-markdown ng-include="pageData.content">
20
+    </div>
21
+  </div>
22
+</div>

+ 0 - 39
app/views/rulebook/index.html

@@ -1,39 +0,0 @@
1
-<div class="container-fluid" >
2
-  <div class="row">
3
-    <div class="col-sm-3 col-md-2 sidebar sidebar-main">
4
-      <h1>docs</h1>
5
-      <ul class="nav nav-sidebar">
6
-        <li><a ui-sref="getting-started">Getting Started</a></li>
7
-        <li class="active"><a ui-sref="rulebook.intro">Rulebook <span class="sr-only">(current)</span></a></li>
8
-        <li><a ui-sref="styleguide">Styleguide</a></li>
9
-        <li><a ui-sref="rest-api-v1.get-missions">Restfull API v1</a></li>
10
-        <li><a ui-sref="realtime-api-v1">Realtime API v1</a></li>
11
-      </ul>
12
-    </div>
13
-    <div class="col-sm-4 col-md-3 col-md-offset-2 col-sm-offset-3 sidebar sidebar-secondary">
14
-      <ul class="nav nav-sidebar">
15
-        <li ng-class="{ active: isActive('/rulebook/intro')}">
16
-          <a ui-sref="rulebook.intro">
17
-            <h5>Introduction</h5>
18
-            <p class="endpoint-description"><i>What is Avalanche Network?</i></p>
19
-          </a>
20
-        </li>
21
-        <li ng-class="{ active: isActive('/rulebook/mission')}">
22
-          <a ui-sref="rulebook.mission">
23
-            <h5>Mission Basics</h5>
24
-            <p class="endpoint-description"><i>Mechanics of how a mission works</i></p>
25
-          </a>
26
-        </li>
27
-        <li ng-class="{ active: isActive('/rulebook/tasks-validations')}">
28
-          <a ui-sref="rulebook.tasks-validations">
29
-            <h5>Tasks & Validations</h5>
30
-            <p class="endpoint-description"><i>How to automaticaly validate tasks</i></p>
31
-          </a>
32
-        </li>
33
-      </ul>
34
-    </div>
35
-    <div class="col-sm-5 col-sm-offset-7 col-md-7 col-md-offset-5 main" ui-view>
36
-
37
-    </div>
38
-  </div>
39
-</div>

+ 0 - 6
app/views/rulebook/introduction.html

@@ -1,6 +0,0 @@
1
-
2
-
3
-<div class="api-endpoint">
4
-  <h2 style="margin-bottom: 0px;">Introduction</h2>
5
-  <hr>
6
-</div>

+ 0 - 6
app/views/rulebook/mission.html

@@ -1,6 +0,0 @@
1
-
2
-
3
-<div class="api-endpoint">
4
-  <h2 style="margin-bottom: 0px;">Mission</h2>
5
-  <hr>
6
-</div>

+ 3 - 1
bower.json

@@ -14,7 +14,9 @@
14 14
     "angular-highlightjs": "~0.4.3",
15 15
     "json-formatter": "~0.3.1",
16 16
     "angular-spinner": "~0.6.2",
17
-    "angular-ui-router": "~0.2.15"
17
+    "angular-ui-router": "~0.2.15",
18
+    "angular-markdown-directive": "~0.3.1",
19
+    "ng-json-explorer": "*"
18 20
   },
19 21
   "devDependencies": {
20 22
     "angular-mocks": "^1.3.0"

+ 4 - 0
test/karma.conf.js

@@ -37,6 +37,10 @@ module.exports = function(config) {
37 37
       'bower_components/spin.js/spin.js',
38 38
       'bower_components/angular-spinner/angular-spinner.js',
39 39
       'bower_components/angular-ui-router/release/angular-ui-router.js',
40
+      'bower_components/showdown/src/showdown.js',
41
+      'bower_components/angularjs/angular.js',
42
+      'bower_components/angular-markdown-directive/markdown.js',
43
+      'bower_components/ng-json-explorer/dist/angular-json-explorer.js',
40 44
       'bower_components/angular-mocks/angular-mocks.js',
41 45
       // endbower
42 46
       "app/scripts/**/*.js",